diff --git a/build.gradle b/build.gradle index 66b7bca6..c1d2f662 100644 --- a/build.gradle +++ b/build.gradle @@ -14,14 +14,16 @@ preprocess { def mc1201 = createNode('1.20.1', 1_20_01, 'yarn') def mc1202 = createNode('1.20.2', 1_20_02, 'yarn') def mc1204 = createNode('1.20.4', 1_20_04, 'yarn') - def mc1205 = createNode('1.20.5', 1_20_05, 'yarn') + def mc1206 = createNode('1.20.6', 1_20_06, 'yarn') + def mc1210 = createNode('1.21.0', 1_21_00, 'yarn') // mapping difference map // base 1194 -> 1193 -> 1192 ->1190-> 118 -> 117 -> 116 mc1202.link(mc1201, file('versions/mapping-1.20.2-1.20.1.txt')) mc1201.link(mc1194, file('versions/mapping-1.20.1-1.19.4.txt')) mc1204.link(mc1202, file('versions/mapping-1.20.4-1.20.2.txt')) - mc1205.link(mc1204, file('versions/mapping-1.20.5-1.20.4.txt')) + mc1206.link(mc1204, file('versions/mapping-1.20.6-1.20.4.txt')) + mc1210.link(mc1206, file('versions/mapping-1.21.0-1.20.6.txt')) //mc1194.link(mc1193, file('versions/mapping-1.19.4-1.19.3.txt')) //mc1193.link(mc1192, file('versions/mapping-1.19.3-1.19.2.txt')) //mc1192.link(mc1190, file('versions/mapping-1.19.2-1.19.0.txt')) diff --git a/common.gradle b/common.gradle index de8f94a7..5724f4c2 100644 --- a/common.gradle +++ b/common.gradle @@ -11,7 +11,7 @@ preprocess { tabIndentation = true } -if (mcVersion > 12004) { +if (mcVersion >= 12005) { sourceCompatibility = JavaVersion.VERSION_21 targetCompatibility = JavaVersion.VERSION_21 } @@ -99,7 +99,7 @@ tasks.withType(JavaCompile).configureEach { // If Javadoc is generated, this must be specified in that task too. it.options.encoding = "UTF-8" - if (mcVersion > 12004) { + if (mcVersion >= 12005) { it.options.release = 21 } else if (mcVersion >= 11800) { diff --git a/external/litematica-fabric-1.20.5-pre1-0.18.0-beta.1.jar b/external/litematica-fabric-1.20.6-0.17.999-sakura.8.jar similarity index 52% rename from external/litematica-fabric-1.20.5-pre1-0.18.0-beta.1.jar rename to external/litematica-fabric-1.20.6-0.17.999-sakura.8.jar index 87bffa1b..bd3bb291 100644 Binary files a/external/litematica-fabric-1.20.5-pre1-0.18.0-beta.1.jar and b/external/litematica-fabric-1.20.6-0.17.999-sakura.8.jar differ diff --git a/external/litematica-fabric-1.21-pre2-0.17.999-snap.jar b/external/litematica-fabric-1.21-pre2-0.17.999-snap.jar new file mode 100644 index 00000000..2f1144c2 Binary files /dev/null and b/external/litematica-fabric-1.21-pre2-0.17.999-snap.jar differ diff --git a/external/malilib-fabric-1.20.5-pre1-0.19.0-beta.1.jar b/external/malilib-fabric-1.20.5-pre1-0.19.0-beta.1.jar deleted file mode 100644 index 7b6e6c5e..00000000 Binary files a/external/malilib-fabric-1.20.5-pre1-0.19.0-beta.1.jar and /dev/null differ diff --git a/external/malilib-fabric-1.20.6-0.18.999-sakura.4.jar b/external/malilib-fabric-1.20.6-0.18.999-sakura.4.jar new file mode 100644 index 00000000..5349e8e2 Binary files /dev/null and b/external/malilib-fabric-1.20.6-0.18.999-sakura.4.jar differ diff --git a/external/malilib-fabric-1.21-pre2-0.18.999-snap.jar b/external/malilib-fabric-1.21-pre2-0.18.999-snap.jar new file mode 100644 index 00000000..d7b1c87f Binary files /dev/null and b/external/malilib-fabric-1.21-pre2-0.18.999-snap.jar differ diff --git a/external/modmenu-v10.0.0-alpha.3+1.20.5.c36bdf3.jar b/external/modmenu-v10.0.0-alpha.3+1.20.5.c36bdf3.jar deleted file mode 100644 index 735c860a..00000000 Binary files a/external/modmenu-v10.0.0-alpha.3+1.20.5.c36bdf3.jar and /dev/null differ diff --git a/gradle.properties b/gradle.properties index dd8d2fe7..35f908ad 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,9 +2,9 @@ org.gradle.jvmargs=-Xmx6G # This is base gradle.properties file for all projects in the workspace. # This must not contain specific configuration for any project, such as minecraft version, etc. # Fabric Properties - loader_version=0.15.9 + loader_version=0.15.11 # Mod Properties - mod_version = 7.2.0-beta.1 + mod_version = 7.2.0 maven_group = aria1th.extensions archives_base_name = litematica-printer diff --git a/settings.gradle b/settings.gradle index 45747430..00a423f6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -35,7 +35,8 @@ def versions = Arrays.asList( "1.20.1", "1.20.2", "1.20.4", - "1.20.5" + "1.20.6", + "1.21.0" ) for (String version : versions) { diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 545b4fb2..a2c01310 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -26,14 +26,14 @@ ], "depends": { - "fabricloader": ">=0.15.9", + "fabricloader": ">=0.15.10", "fabric": "*", "minecraft": "${minecraft_dependency}", "litematica": "*" }, "custom": { "modmenu": { - "parent": "malilib" + "parent": "litematica" } } } diff --git a/versions/1.20.5/gradle.properties b/versions/1.20.5/gradle.properties deleted file mode 100644 index a49319ea..00000000 --- a/versions/1.20.5/gradle.properties +++ /dev/null @@ -1,16 +0,0 @@ -# Fabric Properties - # check these on https://fabricmc.net/use - minecraft_version=1.20.5-pre1 - yarn_mappings=1.20.5-pre1+build.5 - fabricapi_version=0.96.15+1.20.5 - minecraft_version_out = 1.20.5-beta.1 - -# Mod Properties - #malilib_projectid=303119 - #malilib_fileid=4946328 - #litematica_fileid=4946471 - #litematica_projectid=308892 - essentialclient_filename=essential-client-1.20.1-1.3.6.jar - malilib_filename=malilib-fabric-1.20.5-pre1-0.19.0-beta.1.jar - litematica_filename=litematica-fabric-1.20.5-pre1-0.18.0-beta.1.jar - mod_menu_filename=modmenu-v10.0.0-alpha.3+1.20.5.c36bdf3.jar diff --git a/versions/1.20.6/gradle.properties b/versions/1.20.6/gradle.properties new file mode 100644 index 00000000..9364d994 --- /dev/null +++ b/versions/1.20.6/gradle.properties @@ -0,0 +1,15 @@ +# Fabric Properties + # check these on https://fabricmc.net/use + minecraft_version=1.20.6 + yarn_mappings=1.20.6+build.3 + fabricapi_version=0.97.8+1.20.6 + minecraft_version_out = 1.20.6 + +# Mod Properties + #malilib_projectid=303119 + #malilib_fileid=4946328 + #litematica_fileid=4946471 + #litematica_projectid=308892 + essentialclient_filename=essential-client-1.20.1-1.3.6.jar + malilib_filename=malilib-fabric-1.20.6-0.18.999-sakura.4.jar + litematica_filename=litematica-fabric-1.20.6-0.17.999-sakura.8.jar diff --git a/versions/1.20.5/src/main/java/io/github/eatmyvenom/litematicin/mixin/Litematica/ChunkRendererSchematicVboMixin.java b/versions/1.20.6/src/main/java/io/github/eatmyvenom/litematicin/mixin/Litematica/ChunkRendererSchematicVboMixin.java similarity index 88% rename from versions/1.20.5/src/main/java/io/github/eatmyvenom/litematicin/mixin/Litematica/ChunkRendererSchematicVboMixin.java rename to versions/1.20.6/src/main/java/io/github/eatmyvenom/litematicin/mixin/Litematica/ChunkRendererSchematicVboMixin.java index 0806da48..2d73cf3b 100644 --- a/versions/1.20.5/src/main/java/io/github/eatmyvenom/litematicin/mixin/Litematica/ChunkRendererSchematicVboMixin.java +++ b/versions/1.20.6/src/main/java/io/github/eatmyvenom/litematicin/mixin/Litematica/ChunkRendererSchematicVboMixin.java @@ -7,7 +7,6 @@ import net.minecraft.block.BlockState; import net.minecraft.block.entity.BlockEntity; import net.minecraft.client.render.RenderLayer; -import net.minecraft.client.util.math.MatrixStack; import net.minecraft.item.Item; import net.minecraft.util.math.BlockPos; import org.joml.Matrix4f; @@ -19,7 +18,7 @@ import java.util.Set; -import static io.github.eatmyvenom.litematicin.LitematicaMixinMod.RENDER_ONLY_HOLDING_ITEMS; +import io.github.eatmyvenom.litematicin.LitematicaMixinMod; import static io.github.eatmyvenom.litematicin.utils.InventoryUtils.ITEMS; @Mixin(value = ChunkRendererSchematicVbo.class, priority = 1200) @@ -31,7 +30,7 @@ public class ChunkRendererSchematicVboMixin @Inject(method = "renderBlocksAndOverlay", at = @At("HEAD"), cancellable = true, remap = false) private void onRenderBlocksAndOverlay(BlockPos pos, ChunkRenderDataSchematic data, Set tileEntities, Set usedLayers, Matrix4f matrix4f, BufferBuilderCache buffers, CallbackInfo ci) { - if (!RENDER_ONLY_HOLDING_ITEMS.getBooleanValue()) return; + if (!LitematicaMixinMod.RENDER_ONLY_HOLDING_ITEMS.getBooleanValue()) return; BlockState stateSchematic = this.schematicWorldView.getBlockState(pos); Item item = stateSchematic.getBlock().asItem(); if (!ITEMS.contains(item)) { diff --git a/versions/1.20.6/src/main/java/io/github/eatmyvenom/litematicin/mixin/Litematica/MinecraftClientMixin.java b/versions/1.20.6/src/main/java/io/github/eatmyvenom/litematicin/mixin/Litematica/MinecraftClientMixin.java new file mode 100644 index 00000000..6dd8b271 --- /dev/null +++ b/versions/1.20.6/src/main/java/io/github/eatmyvenom/litematicin/mixin/Litematica/MinecraftClientMixin.java @@ -0,0 +1,59 @@ +package io.github.eatmyvenom.litematicin.mixin.Litematica; + +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.DownloadingTerrainScreen; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.hit.HitResult; +import io.github.eatmyvenom.litematicin.utils.*; + + +@Mixin(MinecraftClient.class) +public abstract class MinecraftClientMixin +{ + @Shadow + public HitResult crosshairTarget; + + @Shadow + @Nullable + public ClientWorld world; + + @Shadow + @Nullable + public abstract ClientPlayNetworkHandler getNetworkHandler(); + + @Shadow + @Nullable + public ClientPlayerEntity player; + + // On join a new world/server + @Inject(at = @At("HEAD"), method = "joinWorld") + public void joinWorld(ClientWorld world, DownloadingTerrainScreen.WorldEntryReason worldEntryReason, CallbackInfo ci) { + Printer.worldBottomY = world.getBottomY(); + Printer.worldTopY = world.getTopY(); + } + + @Inject(at = @At("HEAD"), method = "tick") + public void onPrinterTickCount(CallbackInfo info) { + BedrockBreaker.tick(); + InventoryUtils.tick(); + FakeAccurateBlockPlacement.tick(this.getNetworkHandler(), this.player); + } + + @Inject(at = @At("HEAD"), method = "doItemUse") + public void getIfBlockEntity(CallbackInfo info) { + if (crosshairTarget != null && crosshairTarget.getType() == HitResult.Type.BLOCK && (this.world != null ? this.world.getBlockEntity(((BlockHitResult) crosshairTarget).getBlockPos()) : null) != null) { + ItemInputs.clickedPos = ((BlockHitResult) crosshairTarget).getBlockPos(); + } else { + ItemInputs.clickedPos = null; + } + } +} diff --git a/versions/1.20.5/src/main/java/io/github/eatmyvenom/litematicin/utils/InventoryUtils.java b/versions/1.20.6/src/main/java/io/github/eatmyvenom/litematicin/utils/InventoryUtils.java similarity index 90% rename from versions/1.20.5/src/main/java/io/github/eatmyvenom/litematicin/utils/InventoryUtils.java rename to versions/1.20.6/src/main/java/io/github/eatmyvenom/litematicin/utils/InventoryUtils.java index 45db8cde..6e8420ea 100644 --- a/versions/1.20.5/src/main/java/io/github/eatmyvenom/litematicin/utils/InventoryUtils.java +++ b/versions/1.20.6/src/main/java/io/github/eatmyvenom/litematicin/utils/InventoryUtils.java @@ -11,7 +11,6 @@ import net.minecraft.block.entity.LootableContainerBlockEntity; import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.ClientPlayerEntity; -import net.minecraft.component.ComponentMap; import net.minecraft.component.DataComponentTypes; import net.minecraft.component.type.ContainerComponent; import net.minecraft.entity.player.PlayerInventory; @@ -28,9 +27,6 @@ import java.util.*; import java.util.function.Predicate; -import static io.github.eatmyvenom.litematicin.LitematicaMixinMod.*; -import static io.github.eatmyvenom.litematicin.utils.Printer.*; - public class InventoryUtils { private static int ptr = -1; @@ -52,13 +48,13 @@ public class InventoryUtils { public static void tick() { tickCount++; - if (RENDER_ONLY_HOLDING_ITEMS.getBooleanValue() && tickCount % 20 == 0) { + if (LitematicaMixinMod.RENDER_ONLY_HOLDING_ITEMS.getBooleanValue() && tickCount % 20 == 0) { calculateCache(); } - if (INVENTORY_CACHE_TICKS.getIntegerValue() != 0 && tickCount - lastWorkedTick > INVENTORY_CACHE_TICKS.getIntegerValue()){ + if (LitematicaMixinMod.INVENTORY_CACHE_TICKS.getIntegerValue() != 0 && tickCount - lastWorkedTick > LitematicaMixinMod.INVENTORY_CACHE_TICKS.getIntegerValue()){ clearCache(); } - if (!isSleeping && Configs.Generic.EASY_PLACE_MODE.getBooleanValue() && Configs.Generic.EASY_PLACE_HOLD_ENABLED.getBooleanValue() && Hotkeys.EASY_PLACE_ACTIVATION.getKeybind().isKeybindHeld()) { + if (!Printer.isSleeping && Configs.Generic.EASY_PLACE_MODE.getBooleanValue() && Configs.Generic.EASY_PLACE_HOLD_ENABLED.getBooleanValue() && Hotkeys.EASY_PLACE_ACTIVATION.getKeybind().isKeybindHeld()) { for (int i = 0; i < 9; i++) { if (!usedSlots.containsKey(i)) { continue; @@ -104,28 +100,14 @@ private static void calculateCache() { BlockItem blockItem = (BlockItem) item; if (blockItem.getBlock() instanceof ShulkerBoxBlock) { int invSize = 27; - //NbtCompound compound = stack.getSubNbt("BlockEntityTag"); - //if (compound == null) { - //continue; - //} DefaultedList returnStacks = DefaultedList.ofSize(invSize, ItemStack.EMPTY); - - ComponentMap data = stack.getComponents(); - if (data != null && data.contains(DataComponentTypes.CONTAINER)) - { - ContainerComponent container = data.get(DataComponentTypes.CONTAINER); - //if (compound.contains("Items")) { - //Inventories.readNbt(compound, returnStacks, client.player.getRegistryManager()); - //} - if (container != null) { - container.copyTo(returnStacks); - for (ItemStack returnStack : returnStacks) - { - Item returnItem = returnStack.getItem(); - if (returnItem != null) - { - ITEMS.add(returnItem); - } + ContainerComponent container = stack.getComponents().get(DataComponentTypes.CONTAINER); + if (container != null) { + container.copyTo(returnStacks); + for (ItemStack returnStack : returnStacks) { + Item returnItem = returnStack.getItem(); + if (returnItem != null) { + ITEMS.add(returnItem); } } } @@ -247,7 +229,7 @@ private static boolean exceptToolItems(ItemStack a, ItemStack b) { return a.getItem() == b.getItem(); } boolean isItemEqual = ItemStack.areItemsEqual(a, b); - boolean nbtCondition = PRINTER_IGNORE_NBT.getBooleanValue() || ItemStack.areItemsAndComponentsEqual(a, b); + boolean nbtCondition = LitematicaMixinMod.PRINTER_IGNORE_NBT.getBooleanValue() || ItemStack.areItemsAndComponentsEqual(a, b); return isItemEqual && nbtCondition; } @@ -258,20 +240,20 @@ public static boolean areItemsExactCount(ItemStack a, ItemStack b, boolean allow if (allowNamed) { return areItemsExactAllowNamed(a, b); } - boolean nbtCondition = PRINTER_IGNORE_NBT.getBooleanValue() || ItemStack.areItemsAndComponentsEqual(a, b); + boolean nbtCondition = LitematicaMixinMod.PRINTER_IGNORE_NBT.getBooleanValue() || ItemStack.areItemsAndComponentsEqual(a, b); return ItemStack.areItemsEqual(a, b) && nbtCondition; } public static ItemStack getStackForState(MinecraftClient client, BlockState state, World world, BlockPos pos) { // if state is nether portal block, return FLINT_AND_STEEL if (state.isOf(Blocks.NETHER_PORTAL)) { - if (!PRINTER_LIT_PORTAL_USE_FIRECHARGE.getBooleanValue()) return Items.FLINT_AND_STEEL.getDefaultStack(); + if (!LitematicaMixinMod.PRINTER_LIT_PORTAL_USE_FIRECHARGE.getBooleanValue()) return Items.FLINT_AND_STEEL.getDefaultStack(); else { return Items.FIRE_CHARGE.getDefaultStack(); } } - ItemStack stack = isReplaceableWaterFluidSource(state) && PRINTER_PLACE_ICE.getBooleanValue() ? Items.ICE.getDefaultStack() : MaterialCache.getInstance().getRequiredBuildItemForState(state, world, pos); - if (PRINTER_PRINT_DIRT_VARIANTS.getBooleanValue() && !canPickItem(client, stack)) { + ItemStack stack = Printer.isReplaceableWaterFluidSource(state) && LitematicaMixinMod.PRINTER_PLACE_ICE.getBooleanValue() ? Items.ICE.getDefaultStack() : MaterialCache.getInstance().getRequiredBuildItemForState(state, world, pos); + if (LitematicaMixinMod.PRINTER_PRINT_DIRT_VARIANTS.getBooleanValue() && !Printer.canPickItem(client, stack)) { if (state.isOf(Blocks.FARMLAND)) stack = Items.DIRT.getDefaultStack(); else if (state.isOf(Blocks.DIRT_PATH)) stack = Items.DIRT.getDefaultStack(); } @@ -436,7 +418,7 @@ public static int getSlotWIthStackIgnoreNbt(PlayerInventory inv, ItemStack stack if (defaultSlot != -1) { return defaultSlot; } - if (!PRINTER_IGNORE_NBT.getBooleanValue()) { + if (!LitematicaMixinMod.PRINTER_IGNORE_NBT.getBooleanValue()) { return defaultSlot; } for (int i = 0; i < inv.main.size(); i++) { diff --git a/versions/1.20.5/src/main/java/io/github/eatmyvenom/litematicin/utils/ItemInputs.java b/versions/1.20.6/src/main/java/io/github/eatmyvenom/litematicin/utils/ItemInputs.java similarity index 100% rename from versions/1.20.5/src/main/java/io/github/eatmyvenom/litematicin/utils/ItemInputs.java rename to versions/1.20.6/src/main/java/io/github/eatmyvenom/litematicin/utils/ItemInputs.java diff --git a/versions/1.20.5/src/main/java/io/github/eatmyvenom/litematicin/utils/Printer.java b/versions/1.20.6/src/main/java/io/github/eatmyvenom/litematicin/utils/Printer.java similarity index 92% rename from versions/1.20.5/src/main/java/io/github/eatmyvenom/litematicin/utils/Printer.java rename to versions/1.20.6/src/main/java/io/github/eatmyvenom/litematicin/utils/Printer.java index 1bc243f0..3c5ada47 100644 --- a/versions/1.20.5/src/main/java/io/github/eatmyvenom/litematicin/utils/Printer.java +++ b/versions/1.20.6/src/main/java/io/github/eatmyvenom/litematicin/utils/Printer.java @@ -53,10 +53,7 @@ import java.util.*; import java.util.function.Predicate; -import static io.github.eatmyvenom.litematicin.LitematicaMixinMod.*; -import static io.github.eatmyvenom.litematicin.utils.BedrockBreaker.interactBlock; -import static io.github.eatmyvenom.litematicin.utils.BedrockBreaker.isReplaceable; -import static io.github.eatmyvenom.litematicin.utils.FakeAccurateBlockPlacement.getYaw; +import io.github.eatmyvenom.litematicin.LitematicaMixinMod; import static io.github.eatmyvenom.litematicin.utils.InventoryUtils.*; @SuppressWarnings("ConstantConditions") @@ -115,7 +112,7 @@ public static boolean canPickBlock(MinecraftClient mc, BlockState preference, Bl return false; } // Inventory Cache - if (USE_INVENTORY_CACHE.getBooleanValue() && !ITEMS.isEmpty()) { // if cache is enabled and cache is not empty + if (LitematicaMixinMod.USE_INVENTORY_CACHE.getBooleanValue() && !ITEMS.isEmpty()) { // if cache is enabled and cache is not empty return io.github.eatmyvenom.litematicin.utils.InventoryUtils.swapToItem(mc, stack); } if (!stack.isEmpty() && stack.getItem() != Items.AIR) { @@ -128,7 +125,7 @@ public static boolean canPickBlock(MinecraftClient mc, BlockState preference, Bl MessageHolder.sendDebugMessage(mc.player, "Cannot pick block " + preference.getBlock().getName() + " at " + pos.toShortString() + " because no slot"); return false; } - if (EASY_PLACE_MODE_HOTBAR_ONLY.getBooleanValue()) { + if (LitematicaMixinMod.EASY_PLACE_MODE_HOTBAR_ONLY.getBooleanValue()) { boolean isHotbar = slot < 9; if (!isHotbar) { MessageHolder.sendDebugMessage(mc.player, "Cannot pick block " + preference.getBlock().getName() + " at " + pos.toShortString() + " because not in hotbar"); @@ -143,7 +140,7 @@ public static boolean canPickBlock(MinecraftClient mc, BlockState preference, Bl MessageHolder.sendDebugMessage(mc.player, "Cannot pick block " + preference.getBlock().getName() + " at " + pos.toShortString() + " because no slot"); return false; } - if (EASY_PLACE_MODE_HOTBAR_ONLY.getBooleanValue()) { + if (LitematicaMixinMod.EASY_PLACE_MODE_HOTBAR_ONLY.getBooleanValue()) { boolean isHotbar = slot < 9; if (!isHotbar) { MessageHolder.sendDebugMessage(mc.player, "Cannot pick block " + preference.getBlock().getName() + " at " + pos.toShortString() + " because not in hotbar"); @@ -165,7 +162,7 @@ public static boolean canPickItem(MinecraftClient mc, ItemStack stack) { if (slot == -1) { return false; } - if (EASY_PLACE_MODE_HOTBAR_ONLY.getBooleanValue()) { + if (LitematicaMixinMod.EASY_PLACE_MODE_HOTBAR_ONLY.getBooleanValue()) { return slot < 9; } } @@ -187,7 +184,7 @@ synchronized public static boolean doSchematicWorldPickBlock(MinecraftClient mc, return false; } if (!stack.isEmpty()) { - if (USE_INVENTORY_CACHE.getBooleanValue()) { + if (LitematicaMixinMod.USE_INVENTORY_CACHE.getBooleanValue()) { boolean swapResult = io.github.eatmyvenom.litematicin.utils.InventoryUtils.swapToItem(mc, stack); MessageHolder.sendDebugMessage(mc.player, "Swapped to " + stack.getItem().getName() + " at " + pos.toShortString() + " with result " + swapResult); return swapResult; @@ -210,7 +207,7 @@ synchronized public static boolean doSchematicWorldPickBlock(MinecraftClient mc, return false; } if (!stack.isEmpty()) { - if (USE_INVENTORY_CACHE.getBooleanValue()) { + if (LitematicaMixinMod.USE_INVENTORY_CACHE.getBooleanValue()) { return io.github.eatmyvenom.litematicin.utils.InventoryUtils.swapToItem(mc, stack); } else { fi.dy.masa.malilib.util.InventoryUtils.swapItemToMainHand(stack, mc); @@ -292,15 +289,15 @@ public static ActionResult doEasyPlaceNormally(MinecraftClient mc) { //force nor Vec3d hitPos; Direction sideOrig = trace.getSide(); Direction side = applyPlacementFacing(schematicState, sideOrig, clientState); - if (PRINTER_ACCURATE_BLOCK_PLACEMENT.getBooleanValue()) { + if (LitematicaMixinMod.PRINTER_ACCURATE_BLOCK_PLACEMENT.getBooleanValue()) { hitPos = applyCarpetProtocolHitVec(blockPos, schematicState); } else { hitPos = applyHitVec(blockPos, schematicState, side); } BlockHitResult hitResult = new BlockHitResult(hitPos, side, blockPos, false); boolean canContinue; - if (!PRINTER_FAKE_ROTATION.getBooleanValue() || PRINTER_ACCURATE_BLOCK_PLACEMENT.getBooleanValue()) { //Accurateblockplacement, or vanilla but no fake - canContinue = interactBlock(mc, hitResult).isAccepted(); //PLACE block + if (!LitematicaMixinMod.PRINTER_FAKE_ROTATION.getBooleanValue() || LitematicaMixinMod.PRINTER_ACCURATE_BLOCK_PLACEMENT.getBooleanValue()) { //Accurateblockplacement, or vanilla but no fake + canContinue = BedrockBreaker.interactBlock(mc, hitResult).isAccepted(); //PLACE block cacheEasyPlacePosition(blockPos, false); } else { canContinue = FakeAccurateBlockPlacement.request(schematicState, blockPos); @@ -391,10 +388,10 @@ private static boolean isPositionWithinBox(BlockPos pos) { @Environment(EnvType.CLIENT) synchronized public static ActionResult doPrinterAction(MinecraftClient mc) { io.github.eatmyvenom.litematicin.utils.InventoryUtils.itemChangeCount = 0; - if (!DEBUG_MESSAGE.getBooleanValue()) { + if (!LitematicaMixinMod.DEBUG_MESSAGE.getBooleanValue()) { causeMap.clear(); //reduce ram usage } - if (!PRINTER_BEDROCK_BREAKING.getBooleanValue()) { + if (!LitematicaMixinMod.PRINTER_BEDROCK_BREAKING.getBooleanValue()) { BedrockBreaker.clear(); } FakeAccurateBlockPlacement.requestedTicks = Math.max(-2, FakeAccurateBlockPlacement.requestedTicks); @@ -402,14 +399,14 @@ synchronized public static ActionResult doPrinterAction(MinecraftClient mc) { mc.player.sendMessage(Text.of("Handling breakBlock!"), true); return ActionResult.SUCCESS; } - if (PRINTER_ALLOW_INVENTORY_OPERATIONS.getBooleanValue()) { + if (LitematicaMixinMod.PRINTER_ALLOW_INVENTORY_OPERATIONS.getBooleanValue()) { ItemInputs.execute(mc); mc.player.sendMessage(Text.of("Handling inventory operation!"), true); return ActionResult.PASS; } else { ItemInputs.clear(); } - if (new Date().getTime() < lastPlaced + 1000.0 * EASY_PLACE_MODE_DELAY.getDoubleValue()) { + if (new Date().getTime() < lastPlaced + 1000.0 * LitematicaMixinMod.EASY_PLACE_MODE_DELAY.getDoubleValue()) { mc.player.sendMessage(Text.of("Handling delay"), true); return ActionResult.PASS; } else { @@ -431,11 +428,11 @@ synchronized public static ActionResult doPrinterAction(MinecraftClient mc) { posZ = tracePos.getZ(); } - boolean ClearArea = PRINTER_CLEAR_FLUIDS.getBooleanValue(); // if it's true, will ignore everything and remove fluids. - boolean UseCobble = PRINTER_CLEAR_FLUIDS_USE_COBBLESTONE.getBooleanValue() && ClearArea; - boolean ClearSnow = PRINTER_CLEAR_SNOW_LAYER.getBooleanValue() && ClearArea; - boolean CanUseProtocol = PRINTER_ACCURATE_BLOCK_PLACEMENT.getBooleanValue(); - boolean FillInventory = PRINTER_PUMPKIN_PIE_FOR_COMPOSTER.getBooleanValue(); + boolean ClearArea = LitematicaMixinMod.PRINTER_CLEAR_FLUIDS.getBooleanValue(); // if it's true, will ignore everything and remove fluids. + boolean UseCobble = LitematicaMixinMod.PRINTER_CLEAR_FLUIDS_USE_COBBLESTONE.getBooleanValue() && ClearArea; + boolean ClearSnow = LitematicaMixinMod.PRINTER_CLEAR_SNOW_LAYER.getBooleanValue() && ClearArea; + boolean CanUseProtocol = LitematicaMixinMod.PRINTER_ACCURATE_BLOCK_PLACEMENT.getBooleanValue(); + boolean FillInventory = LitematicaMixinMod.PRINTER_PUMPKIN_PIE_FOR_COMPOSTER.getBooleanValue(); ItemStack composableItem = Items.PUMPKIN_PIE.getDefaultStack(); //#if MC>=11902 List allPlacementsTouchingSubChunk = DataManager.getSchematicPlacementManager().getAllPlacementsTouchingChunk(tracePos); @@ -445,7 +442,7 @@ synchronized public static ActionResult doPrinterAction(MinecraftClient mc) { Box selectedBox = null; Printer.CURRENT_BOX = null; if (allPlacementsTouchingSubChunk.isEmpty() && !ClearArea) { - if (PRINTER_BEDROCK_BREAKING.getBooleanValue()) { + if (LitematicaMixinMod.PRINTER_BEDROCK_BREAKING.getBooleanValue()) { BedrockBreaker.scheduledTickHandler(mc, null); } return ActionResult.PASS; @@ -456,9 +453,9 @@ synchronized public static ActionResult doPrinterAction(MinecraftClient mc) { int minX = 0; int minY = 0; int minZ = 0; - int rangeX = EASY_PLACE_MODE_RANGE_X.getIntegerValue(); - int rangeY = EASY_PLACE_MODE_RANGE_Y.getIntegerValue(); - int rangeZ = EASY_PLACE_MODE_RANGE_Z.getIntegerValue(); + int rangeX = LitematicaMixinMod.EASY_PLACE_MODE_RANGE_X.getIntegerValue(); + int rangeY = LitematicaMixinMod.EASY_PLACE_MODE_RANGE_Y.getIntegerValue(); + int rangeZ = LitematicaMixinMod.EASY_PLACE_MODE_RANGE_Z.getIntegerValue(); if (rangeX == 0 && rangeY == 0 && rangeZ == 0 && traceWrapper != null) { return doEasyPlaceNormally(mc); } @@ -509,17 +506,17 @@ synchronized public static ActionResult doPrinterAction(MinecraftClient mc) { } if (!foundBox) { - if (PRINTER_BEDROCK_BREAKING.getBooleanValue()) { + if (LitematicaMixinMod.PRINTER_BEDROCK_BREAKING.getBooleanValue()) { BedrockBreaker.scheduledTickHandler(mc, null); } return ActionResult.PASS; } LayerRange range = DataManager.getRenderLayerRange(); //add range following int MaxReach = Math.max(Math.max(rangeX, rangeY), rangeZ); - boolean breakBlocks = PRINTER_BREAK_BLOCKS.getBooleanValue(); - boolean ExplicitObserver = PRINTER_OBSERVER_AVOID_ALL.getBooleanValue(); - boolean flipBlocks = mc.player.getMainHandStack().getItem().equals(Items.CACTUS) && PRINTER_FLIPPINCACTUS.getBooleanValue(); //if true, will flip blocks with cactus - boolean smartRedstone = PRINTER_SMART_REDSTONE_AVOID.getBooleanValue(); + boolean breakBlocks = LitematicaMixinMod.PRINTER_BREAK_BLOCKS.getBooleanValue(); + boolean ExplicitObserver = LitematicaMixinMod.PRINTER_OBSERVER_AVOID_ALL.getBooleanValue(); + boolean flipBlocks = mc.player.getMainHandStack().getItem().equals(Items.CACTUS) && LitematicaMixinMod.PRINTER_FLIPPINCACTUS.getBooleanValue(); //if true, will flip blocks with cactus + boolean smartRedstone = LitematicaMixinMod.PRINTER_SMART_REDSTONE_AVOID.getBooleanValue(); Direction[] facingSides = Direction.getEntityFacingOrder(mc.player); Direction primaryFacing = facingSides[0]; Direction horizontalFacing = primaryFacing; // For use in blocks with only horizontal rotation @@ -537,7 +534,7 @@ synchronized public static ActionResult doPrinterAction(MinecraftClient mc) { * MC works) */ - int maxInteract = PRINTER_MAX_BLOCKS.getIntegerValue(); + int maxInteract = LitematicaMixinMod.PRINTER_MAX_BLOCKS.getIntegerValue(); int interact = 0; int fromX = Math.max(posX - rangeX, minX); @@ -564,7 +561,7 @@ synchronized public static ActionResult doPrinterAction(MinecraftClient mc) { if (interact >= maxInteract) { if (shouldSleepLonger) { shouldSleepLonger = false; - lastPlaced = Math.max(lastPlaced, new Date().getTime() + PRINTER_SLEEP_STACK_EMPTIED.getIntegerValue()); + lastPlaced = Math.max(lastPlaced, new Date().getTime() + LitematicaMixinMod.PRINTER_SLEEP_STACK_EMPTIED.getIntegerValue()); } else { lastPlaced = Math.max(lastPlaced, new Date().getTime()); } @@ -586,13 +583,13 @@ synchronized public static ActionResult doPrinterAction(MinecraftClient mc) { } BlockPos pos = new BlockPos(x, y, z); - if (PRINTER_ALLOW_INVENTORY_OPERATIONS.getBooleanValue() && io.github.eatmyvenom.litematicin.utils.InventoryUtils.hasItemInSchematic(world, pos)) { + if (LitematicaMixinMod.PRINTER_ALLOW_INVENTORY_OPERATIONS.getBooleanValue() && io.github.eatmyvenom.litematicin.utils.InventoryUtils.hasItemInSchematic(world, pos)) { MessageHolder.sendUniqueMessageAlways("Inventory in " + pos.toShortString() + " has Item inside!"); } BlockState stateSchematic; BlockState stateClient; updateSignText(mc, world, pos); - if (!breakBlocks && !ClearArea && !flipBlocks && !PRINTER_BEDROCK_BREAKING.getBooleanValue()) { + if (!breakBlocks && !ClearArea && !flipBlocks && !LitematicaMixinMod.PRINTER_BEDROCK_BREAKING.getBooleanValue()) { if (world.isAir(pos)) { continue; } else { @@ -602,7 +599,7 @@ synchronized public static ActionResult doPrinterAction(MinecraftClient mc) { } } if (breakBlocks) { - if (PRINTER_BREAK_IGNORE_EXTRA.getBooleanValue() && world.isAir(pos)) { + if (LitematicaMixinMod.PRINTER_BREAK_IGNORE_EXTRA.getBooleanValue() && world.isAir(pos)) { continue; } } @@ -627,22 +624,22 @@ synchronized public static ActionResult doPrinterAction(MinecraftClient mc) { if (interact >= maxInteract) { if (shouldSleepLonger) { shouldSleepLonger = false; - lastPlaced = Math.max(lastPlaced, new Date().getTime() + PRINTER_SLEEP_STACK_EMPTIED.getIntegerValue()); + lastPlaced = Math.max(lastPlaced, new Date().getTime() + LitematicaMixinMod.PRINTER_SLEEP_STACK_EMPTIED.getIntegerValue()); } else { lastPlaced = Math.max(lastPlaced, new Date().getTime()); } return ActionResult.SUCCESS; } - } else if (BedrockBreaker.isBlockNotInstantBreakable(stateClient.getBlock()) && PRINTER_BEDROCK_BREAKING.getBooleanValue()) { + } else if (BedrockBreaker.isBlockNotInstantBreakable(stateClient.getBlock()) && LitematicaMixinMod.PRINTER_BEDROCK_BREAKING.getBooleanValue()) { mc.player.sendMessage(Text.of("Handling printerBedrockBreaking!"), true); interact += BedrockBreaker.scheduledTickHandler(mc, pos); continue; - } else if (PRINTER_BEDROCK_BREAKING.getBooleanValue()) { + } else if (LitematicaMixinMod.PRINTER_BEDROCK_BREAKING.getBooleanValue()) { mc.player.sendMessage(Text.of("Handling printerBedrockBreaking!"), true); interact += BedrockBreaker.scheduledTickHandler(mc, null); continue; } else if (!positionStorage.hasPos(pos)) { // For survival - boolean replaceable = isReplaceable(mc.world.getBlockState(pos)); + boolean replaceable = BedrockBreaker.isReplaceable(mc.world.getBlockState(pos)); if (!replaceable && mc.world.getBlockState(pos).getHardness(world, pos) == -1) { continue; } @@ -679,7 +676,7 @@ synchronized public static ActionResult doPrinterAction(MinecraftClient mc) { if (facingSchematic == facingClient) { int clickTimes = 0; Direction side = Direction.NORTH; - if (sBlock instanceof RepeaterBlock && !PRINTER_ACCURATE_BLOCK_PLACEMENT.getBooleanValue()) { + if (sBlock instanceof RepeaterBlock && !LitematicaMixinMod.PRINTER_ACCURATE_BLOCK_PLACEMENT.getBooleanValue()) { int clientDelay = stateClient.get(RepeaterBlock.DELAY); int schematicDelay = stateSchematic.get(RepeaterBlock.DELAY); if (clientDelay != schematicDelay) { @@ -691,7 +688,7 @@ synchronized public static ActionResult doPrinterAction(MinecraftClient mc) { } } side = Direction.UP; - } else if (sBlock instanceof ComparatorBlock && !PRINTER_ACCURATE_BLOCK_PLACEMENT.getBooleanValue()) { + } else if (sBlock instanceof ComparatorBlock && !LitematicaMixinMod.PRINTER_ACCURATE_BLOCK_PLACEMENT.getBooleanValue()) { if (stateSchematic.get(ComparatorBlock.MODE) != stateClient .get(ComparatorBlock.MODE)) { clickTimes = 1; @@ -751,12 +748,12 @@ synchronized public static ActionResult doPrinterAction(MinecraftClient mc) { if (doSchematicWorldPickBlock(mc, composableItem)) { Vec3d hitPos = new Vec3d(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5); BlockHitResult hitResult = new BlockHitResult(hitPos, side, pos, false); - interactBlock(mc, hitResult); //COMPOSTER + BedrockBreaker.interactBlock(mc, hitResult); //COMPOSTER io.github.eatmyvenom.litematicin.utils.InventoryUtils.decrementCount(isCreative); cacheEasyPlacePosition(pos, false); if (shouldSleepLonger) { shouldSleepLonger = false; - lastPlaced = Math.max(lastPlaced, new Date().getTime() + 200 + PRINTER_SLEEP_STACK_EMPTIED.getIntegerValue()); + lastPlaced = Math.max(lastPlaced, new Date().getTime() + 200 + LitematicaMixinMod.PRINTER_SLEEP_STACK_EMPTIED.getIntegerValue()); } else { lastPlaced = Math.max(lastPlaced, new Date().getTime() + 200); } @@ -765,7 +762,7 @@ synchronized public static ActionResult doPrinterAction(MinecraftClient mc) { } else { cacheEasyPlacePosition(pos, true); } - } else if (!isPositionCached(pos, false) && PRINTER_PLACE_MINECART.getBooleanValue() && sBlock instanceof DetectorRailBlock && cBlock instanceof DetectorRailBlock) { + } else if (!isPositionCached(pos, false) && LitematicaMixinMod.PRINTER_PLACE_MINECART.getBooleanValue() && sBlock instanceof DetectorRailBlock && cBlock instanceof DetectorRailBlock) { if (!shouldAvoidPlaceCart(pos, world) && placeCart(stateSchematic, mc, pos)) { continue; } @@ -776,7 +773,7 @@ synchronized public static ActionResult doPrinterAction(MinecraftClient mc) { BlockHitResult hitResult = new BlockHitResult(hitPos, side, pos, false); - interactBlock(mc, hitResult); //NOTEBLOCK, REPEATER... + BedrockBreaker.interactBlock(mc, hitResult); //NOTEBLOCK, REPEATER... interact++; } @@ -788,19 +785,19 @@ synchronized public static ActionResult doPrinterAction(MinecraftClient mc) { } // Blocks are not equal, but can be converted. example: dirt -> dirt path if (stateClient.isOf(Blocks.DIRT)) { - if (stateSchematic.isOf(Blocks.DIRT_PATH) && PRINTER_PRINT_DIRT_VARIANTS.getBooleanValue() && io.github.eatmyvenom.litematicin.utils.InventoryUtils.canSwap(mc.player, item -> item.getItem() instanceof ShovelItem)) { + if (stateSchematic.isOf(Blocks.DIRT_PATH) && LitematicaMixinMod.PRINTER_PRINT_DIRT_VARIANTS.getBooleanValue() && io.github.eatmyvenom.litematicin.utils.InventoryUtils.canSwap(mc.player, item -> item.getItem() instanceof ShovelItem)) { if (doSchematicWorldPickBlock(mc, stack -> stack.getItem() instanceof ShovelItem)){ Vec3d hitPos = new Vec3d(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5); BlockHitResult hitResult = new BlockHitResult(hitPos, Direction.UP, pos, false); - interactBlock(mc, hitResult); + BedrockBreaker.interactBlock(mc, hitResult); } } // farmland - else if (stateSchematic.isOf(Blocks.FARMLAND) && PRINTER_PRINT_DIRT_VARIANTS.getBooleanValue() && io.github.eatmyvenom.litematicin.utils.InventoryUtils.canSwap(mc.player, item -> item.getItem() instanceof HoeItem)) { + else if (stateSchematic.isOf(Blocks.FARMLAND) && LitematicaMixinMod.PRINTER_PRINT_DIRT_VARIANTS.getBooleanValue() && io.github.eatmyvenom.litematicin.utils.InventoryUtils.canSwap(mc.player, item -> item.getItem() instanceof HoeItem)) { if (doSchematicWorldPickBlock(mc, stack -> stack.getItem() instanceof HoeItem)){ Vec3d hitPos = new Vec3d(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5); BlockHitResult hitResult = new BlockHitResult(hitPos, Direction.UP, pos, false); - interactBlock(mc, hitResult); + BedrockBreaker.interactBlock(mc, hitResult); } } } @@ -837,7 +834,7 @@ else if (stateSchematic.isOf(Blocks.FARMLAND) && PRINTER_PRINT_DIRT_VARIANTS.get if (ShapeBoolean) { Vec3d hitPos = Vec3d.ofCenter(pos); BlockHitResult hitResult = new BlockHitResult(hitPos, side, pos, false); - interactBlock(mc, hitResult); //CACTUS + BedrockBreaker.interactBlock(mc, hitResult); //CACTUS cacheEasyPlacePosition(pos, true); interact++; } else if (breakBlocks && ShouldFix) { //cannot fix via flippincactus @@ -855,7 +852,7 @@ else if (stateSchematic.isOf(Blocks.FARMLAND) && PRINTER_PRINT_DIRT_VARIANTS.get mc.player.sendMessage(Text.of("Handling printerFlippinCactus!"), true); continue; } - if (isPositionCached(pos, false) || PRINTER_BEDROCK_BREAKING.getBooleanValue() || (!(stateSchematic.getBlock() instanceof NetherPortalBlock) && stateSchematic.isAir() && !ClearArea)) { + if (isPositionCached(pos, false) || LitematicaMixinMod.PRINTER_BEDROCK_BREAKING.getBooleanValue() || (!(stateSchematic.getBlock() instanceof NetherPortalBlock) && stateSchematic.isAir() && !ClearArea)) { continue; } ItemStack stack = MaterialCache.getInstance().getRequiredBuildItemForState(stateSchematic); @@ -885,7 +882,7 @@ else if (stateSchematic.isOf(Blocks.FARMLAND) && PRINTER_PRINT_DIRT_VARIANTS.get if (ClearArea && doSchematicWorldPickBlock(mc, stack)) { Vec3d hitPos = Vec3d.ofCenter(pos).add(0, 0.5, 0); BlockHitResult hitResult = new BlockHitResult(hitPos, Direction.UP, pos, false); - interactBlock(mc, hitResult); //FLUID REMOVAL + BedrockBreaker.interactBlock(mc, hitResult); //FLUID REMOVAL io.github.eatmyvenom.litematicin.utils.InventoryUtils.decrementCount(isCreative); interact++; cacheEasyPlacePosition(pos, false); @@ -907,7 +904,7 @@ else if (stateSchematic.isOf(Blocks.FARMLAND) && PRINTER_PRINT_DIRT_VARIANTS.get causeMap.remove(pos.asLong()); continue; } - if (cBlock != sBlock && !isReplaceable(stateClient)) { + if (cBlock != sBlock && !BedrockBreaker.isReplaceable(stateClient)) { // Wrong block is in place, requires player action to fix MessageHolder.sendUniqueMessage(mc.player, sBlock.getTranslationKey() + " at " + pos.toShortString() + " is blocking placement of " + cBlock.getTranslationKey() + "!!"); continue; @@ -919,12 +916,12 @@ else if (stateSchematic.isOf(Blocks.FARMLAND) && PRINTER_PRINT_DIRT_VARIANTS.get recordCause(pos, stateSchematic.getBlock().getTranslationKey() + " at " + pos.toShortString() + " is Falling block", pos.down()); MessageHolder.sendUniqueMessage(mc.player, getReason(pos.asLong())); continue; - } else if (!PRINTER_PLACE_ICE.getBooleanValue() && stateSchematic.isOf(Blocks.WATER)) { + } else if (!LitematicaMixinMod.PRINTER_PLACE_ICE.getBooleanValue() && stateSchematic.isOf(Blocks.WATER)) { // Block is water, don't place it recordCause(pos, stateSchematic.getBlock().getTranslationKey() + " at " + pos.toShortString() + " is water", pos); MessageHolder.sendUniqueMessage(mc.player, getReason(pos.asLong())); continue; - } else if (!PRINTER_PLACE_ICE.getBooleanValue() && stateSchematic.isOf(Blocks.LAVA)) { + } else if (!LitematicaMixinMod.PRINTER_PLACE_ICE.getBooleanValue() && stateSchematic.isOf(Blocks.LAVA)) { // Block is lava, don't place it recordCause(pos, stateSchematic.getBlock().getTranslationKey() + " at " + pos.toShortString() + " is lava", pos); MessageHolder.sendUniqueMessage(mc.player, getReason(pos.asLong())); @@ -976,7 +973,7 @@ else if (stateSchematic.isOf(Blocks.FARMLAND) && PRINTER_PRINT_DIRT_VARIANTS.get continue; } if (willExtendInWorld(world, pos, stateSchematic.get(PistonBlock.FACING)) != stateSchematic.get(PistonBlock.EXTENDED) && directlyPowered(world, pos, stateSchematic.get(PistonBlock.FACING))) { - if (PRINTER_SUPPRESS_PUSH_LIMIT.getBooleanValue()) { + if (LitematicaMixinMod.PRINTER_SUPPRESS_PUSH_LIMIT.getBooleanValue()) { recordCause(pos, sBlock.getTranslationKey() + " at " + pos.toShortString() + " should respect push limit because its directly powered", pos); MessageHolder.sendUniqueMessage(mc.player, getReason(pos.asLong())); continue; @@ -985,7 +982,7 @@ else if (stateSchematic.isOf(Blocks.FARMLAND) && PRINTER_PRINT_DIRT_VARIANTS.get } } else if (sBlock instanceof ObserverBlock) { if (ObserverUpdateOrder(mc, world, pos, selectedBox)) { - if (PRINTER_FLIPPINCACTUS.getBooleanValue() && canBypass(mc, world, pos)) { + if (LitematicaMixinMod.PRINTER_FLIPPINCACTUS.getBooleanValue() && canBypass(mc, world, pos)) { stateSchematic = stateSchematic.with(ObserverBlock.FACING, stateSchematic.get(ObserverBlock.FACING).getOpposite()); } else { BlockPos causedPos = ObserverUpdateOrderPos(mc, world, pos); @@ -1055,12 +1052,12 @@ else if (stateSchematic.isOf(Blocks.FARMLAND) && PRINTER_PRINT_DIRT_VARIANTS.get if (doSchematicWorldPickBlock(mc, lightStack)) { Vec3d hitPos = Vec3d.ofCenter(new BlockPos(x, y - 1, z)).add(0, 0.5, 0); BlockHitResult hitResult = new BlockHitResult(hitPos, Direction.UP, new BlockPos(x, y - 1, z), false); - interactBlock(mc, hitResult); //LIGHT + BedrockBreaker.interactBlock(mc, hitResult); //LIGHT cacheEasyPlacePosition(pos, false); sleepWhenRequired(mc); if (shouldSleepLonger) { shouldSleepLonger = false; - lastPlaced = Math.max(lastPlaced, new Date().getTime() + 200 + PRINTER_SLEEP_STACK_EMPTIED.getIntegerValue()); + lastPlaced = Math.max(lastPlaced, new Date().getTime() + 200 + LitematicaMixinMod.PRINTER_SLEEP_STACK_EMPTIED.getIntegerValue()); } else { lastPlaced = Math.max(lastPlaced, new Date().getTime() + 200); interact++; @@ -1079,10 +1076,10 @@ else if (stateSchematic.isOf(Blocks.FARMLAND) && PRINTER_PRINT_DIRT_VARIANTS.get FacingData facedata = FacingData.getFacingData(stateSchematic); if (facedata == null && !(stateSchematic.getBlock() instanceof AbstractRailBlock) && !simulateFacingData(stateSchematic, pos, Vec3d.ofCenter(pos)) ) { MessageHolder.sendMessageUncheckedUnique(mc.player, stateSchematic.getBlock() + " does not have facing data, please add this!"); - if (PRINTER_SKIP_UNKNOWN_BLOCKSTATE.getBooleanValue()) continue; + if (LitematicaMixinMod.PRINTER_SKIP_UNKNOWN_BLOCKSTATE.getBooleanValue()) continue; } - if (!(CanUseProtocol && IsBlockSupportedCarpet(stateSchematic.getBlock())) && !PRINTER_FAKE_ROTATION.getBooleanValue() && !canPlaceFace(facedata, stateSchematic, primaryFacing, horizontalFacing)) { + if (!(CanUseProtocol && IsBlockSupportedCarpet(stateSchematic.getBlock())) && !LitematicaMixinMod.PRINTER_FAKE_ROTATION.getBooleanValue() && !canPlaceFace(facedata, stateSchematic, primaryFacing, horizontalFacing)) { continue; } @@ -1097,7 +1094,7 @@ else if (stateSchematic.isOf(Blocks.FARMLAND) && PRINTER_PRINT_DIRT_VARIANTS.get // Exception for signs (edge case) if (stateSchematic.getBlock() instanceof SignBlock && !(stateSchematic.getBlock() instanceof WallSignBlock)) { - if ((MathHelper.floor((double) ((180.0F + getYaw(mc.player)) * 16.0F / 360.0F) + 0.5D) + if ((MathHelper.floor((double) ((180.0F + FakeAccurateBlockPlacement.getYaw(mc.player)) * 16.0F / 360.0F) + 0.5D) & 15) != stateSchematic.get(SignBlock.ROTATION)) { continue; } @@ -1108,16 +1105,16 @@ else if (stateSchematic.isOf(Blocks.FARMLAND) && PRINTER_PRINT_DIRT_VARIANTS.get Block blockSchematic = stateSchematic.getBlock(); //Don't place waterlogged block's original block before fluid since its painful // 1. if - if (PRINTER_PLACE_ICE.getBooleanValue() && - (isReplaceableWaterFluidSource(stateSchematic) && isReplaceable(stateClient) && !isReplaceableWaterFluidSource(stateClient) && !stateClient.isOf(Blocks.LAVA) || - PRINTER_WATERLOGGED_WATER_FIRST.getBooleanValue() && isReplaceable(stateClient) && containsWaterloggable(stateSchematic)) + if (LitematicaMixinMod.PRINTER_PLACE_ICE.getBooleanValue() && + (isReplaceableWaterFluidSource(stateSchematic) && BedrockBreaker.isReplaceable(stateClient) && !isReplaceableWaterFluidSource(stateClient) && !stateClient.isOf(Blocks.LAVA) || + LitematicaMixinMod.PRINTER_WATERLOGGED_WATER_FIRST.getBooleanValue() && BedrockBreaker.isReplaceable(stateClient) && containsWaterloggable(stateSchematic)) ) { ItemStack iceStack = Items.ICE.getDefaultStack(); if (!FakeAccurateBlockPlacement.canHandleOther(iceStack.getItem())) { continue; } if (doSchematicWorldPickBlock(mc, iceStack)) { - interactBlock(mc, new BlockHitResult(new Vec3d(pos.getX(), pos.getY(), pos.getZ()), Direction.DOWN, pos, false)); + BedrockBreaker.interactBlock(mc, new BlockHitResult(new Vec3d(pos.getX(), pos.getY(), pos.getZ()), Direction.DOWN, pos, false)); io.github.eatmyvenom.litematicin.utils.InventoryUtils.decrementCount(isCreative); cacheEasyPlacePosition(pos, false); sleepWhenRequired(mc); @@ -1145,12 +1142,12 @@ else if (stateSchematic.isOf(Blocks.FARMLAND) && PRINTER_PRINT_DIRT_VARIANTS.get interact++; continue; } - if (blockSchematic instanceof TrapdoorBlock && !CanUseProtocol && !PRINTER_FAKE_ROTATION.getBooleanValue()) { + if (blockSchematic instanceof TrapdoorBlock && !CanUseProtocol && !LitematicaMixinMod.PRINTER_FAKE_ROTATION.getBooleanValue()) { placeTrapDoor(stateSchematic, mc, pos); interact++; continue; } - int miliseconds = EASY_PLACE_CACHE_TIME.getIntegerValue(); + int miliseconds = LitematicaMixinMod.EASY_PLACE_CACHE_TIME.getIntegerValue(); if (blockSchematic instanceof WallMountedBlock || blockSchematic instanceof TorchBlock || blockSchematic instanceof WallSkullBlock || blockSchematic instanceof LadderBlock || blockSchematic instanceof TripwireHookBlock || blockSchematic instanceof WallSignBlock || @@ -1177,7 +1174,7 @@ else if (stateSchematic.isOf(Blocks.FARMLAND) && PRINTER_PRINT_DIRT_VARIANTS.get npos = pos.down(); } if (hasGui(world.getBlockState(npos).getBlock())) { - if (PRINTER_FAKE_ROTATION.getBooleanValue() && interact < maxInteract) { + if (LitematicaMixinMod.PRINTER_FAKE_ROTATION.getBooleanValue() && interact < maxInteract) { if (FakeAccurateBlockPlacement.request(stateSchematic, pos)) { interact++; } @@ -1199,13 +1196,13 @@ else if (stateSchematic.isOf(Blocks.FARMLAND) && PRINTER_PRINT_DIRT_VARIANTS.get //Any : if we have block in testPos, then we can place with wanted direction. //Trapdoors : it can be placed in air with player direction's opposite. //Else : can't be placed except End Rod. - if (!isReplaceable(mc.world.getBlockState(npos))) { + if (!BedrockBreaker.isReplaceable(mc.world.getBlockState(npos))) { //npos is blockPos to be hit. //instead, hitVec should have 1 corresponding to direction property. //but First check if its block with GUI* Block checkGui = mc.world.getBlockState(npos).getBlock(); if (!mc.player.shouldCancelInteraction() && hasGui(checkGui)) { - if (blockSchematic instanceof TrapdoorBlock && PRINTER_FAKE_ROTATION.getBooleanValue() && interact < maxInteract) { + if (blockSchematic instanceof TrapdoorBlock && LitematicaMixinMod.PRINTER_FAKE_ROTATION.getBooleanValue() && interact < maxInteract) { if (FakeAccurateBlockPlacement.request(stateSchematic, pos)) { interact++; } @@ -1230,7 +1227,7 @@ else if (stateSchematic.isOf(Blocks.FARMLAND) && PRINTER_PRINT_DIRT_VARIANTS.get if (stateSchematic.contains(RedstoneTorchBlock.LIT) && !stateSchematic.get(RedstoneTorchBlock.LIT)) { cacheEasyPlacePosition(pos.up(), false, miliseconds); } - interactBlock(mc, new BlockHitResult(hitVec, required, npos, false)); //place block + BedrockBreaker.interactBlock(mc, new BlockHitResult(hitVec, required, npos, false)); //place block io.github.eatmyvenom.litematicin.utils.InventoryUtils.decrementCount(isCreative); sleepWhenRequired(mc); } @@ -1247,7 +1244,7 @@ else if (stateSchematic.isOf(Blocks.FARMLAND) && PRINTER_PRINT_DIRT_VARIANTS.get if (stateSchematic.contains(RedstoneTorchBlock.LIT) && !stateSchematic.get(RedstoneTorchBlock.LIT)) { cacheEasyPlacePosition(pos.up(), false, miliseconds); } - interactBlock(mc, new BlockHitResult(hitVec, Direction.UP, npos, false)); //place block + BedrockBreaker.interactBlock(mc, new BlockHitResult(hitVec, Direction.UP, npos, false)); //place block io.github.eatmyvenom.litematicin.utils.InventoryUtils.decrementCount(isCreative); sleepWhenRequired(mc); } @@ -1262,7 +1259,7 @@ else if (stateSchematic.isOf(Blocks.FARMLAND) && PRINTER_PRINT_DIRT_VARIANTS.get if (stateSchematic.contains(RedstoneTorchBlock.LIT) && !stateSchematic.get(RedstoneTorchBlock.LIT)) { cacheEasyPlacePosition(pos.up(), false, 700); } - interactBlock(mc, new BlockHitResult(hitVec, required, npos, false)); //place block + BedrockBreaker.interactBlock(mc, new BlockHitResult(hitVec, required, npos, false)); //place block io.github.eatmyvenom.litematicin.utils.InventoryUtils.decrementCount(isCreative); sleepWhenRequired(mc); } @@ -1273,7 +1270,7 @@ else if (stateSchematic.isOf(Blocks.FARMLAND) && PRINTER_PRINT_DIRT_VARIANTS.get if (horizontalFacing.getOpposite() == trapdoor) { if (doSchematicWorldPickBlock(mc, stateSchematic, pos)) { cacheEasyPlacePosition(pos, false); - interactBlock(mc, new BlockHitResult(Vec3d.of(pos), + BedrockBreaker.interactBlock(mc, new BlockHitResult(Vec3d.of(pos), stateSchematic.get(TrapdoorBlock.FACING).getOpposite(), pos, false)); //place block io.github.eatmyvenom.litematicin.utils.InventoryUtils.decrementCount(isCreative); sleepWhenRequired(mc); @@ -1286,7 +1283,7 @@ else if (stateSchematic.isOf(Blocks.FARMLAND) && PRINTER_PRINT_DIRT_VARIANTS.get if ((primaryFacing.getAxis() == Direction.Axis.Y && horizontalFacing == direction) || (primaryFacing.getAxis() != Direction.Axis.Y && horizontalFacing == direction.getOpposite())) { if (doSchematicWorldPickBlock(mc, stateSchematic, pos)) { cacheEasyPlacePosition(pos, false); - interactBlock(mc, new BlockHitResult(Vec3d.of(pos), + BedrockBreaker.interactBlock(mc, new BlockHitResult(Vec3d.of(pos), stateSchematic.get(GrindstoneBlock.FACING).getOpposite(), pos, false)); //place block io.github.eatmyvenom.litematicin.utils.InventoryUtils.decrementCount(isCreative); sleepWhenRequired(mc); @@ -1298,7 +1295,7 @@ else if (stateSchematic.isOf(Blocks.FARMLAND) && PRINTER_PRINT_DIRT_VARIANTS.get if (blockSchematic instanceof EndRodBlock) { if (doSchematicWorldPickBlock(mc, stateSchematic, pos)) { cacheEasyPlacePosition(pos, false); - interactBlock(mc, new BlockHitResult(Vec3d.ofCenter(pos), + BedrockBreaker.interactBlock(mc, new BlockHitResult(Vec3d.ofCenter(pos), stateSchematic.get(EndRodBlock.FACING), pos, false)); //place block io.github.eatmyvenom.litematicin.utils.InventoryUtils.decrementCount(isCreative); interact++; @@ -1333,7 +1330,7 @@ else if (stateSchematic.isOf(Blocks.FARMLAND) && PRINTER_PRINT_DIRT_VARIANTS.get if (doSchematicWorldPickBlock(mc, stateSchematic, pos)) { cacheEasyPlacePosition(pos, false); interact++; - interactBlock(mc, hitResult); //SNOW LAYERS + BedrockBreaker.interactBlock(mc, hitResult); //SNOW LAYERS io.github.eatmyvenom.litematicin.utils.InventoryUtils.decrementCount(isCreative); sleepWhenRequired(mc); } @@ -1353,10 +1350,10 @@ else if (stateSchematic.isOf(Blocks.FARMLAND) && PRINTER_PRINT_DIRT_VARIANTS.get }); } } - if (!PRINTER_FAKE_ROTATION.getBooleanValue() || PRINTER_ACCURATE_BLOCK_PLACEMENT.getBooleanValue()) { //Accurateblockplacement, or vanilla but no fake + if (!LitematicaMixinMod.PRINTER_FAKE_ROTATION.getBooleanValue() || LitematicaMixinMod.PRINTER_ACCURATE_BLOCK_PLACEMENT.getBooleanValue()) { //Accurateblockplacement, or vanilla but no fake if (doSchematicWorldPickBlock(mc, stateSchematic, pos)) { MessageHolder.sendOrderMessage("Places block " + blockSchematic + " at " + pos.toShortString()); - interactBlock(mc, hitResult); //PLACE BLOCK + BedrockBreaker.interactBlock(mc, hitResult); //PLACE BLOCK io.github.eatmyvenom.litematicin.utils.InventoryUtils.decrementCount(isCreative); cacheEasyPlacePosition(pos, false); sleepWhenRequired(mc); @@ -1379,7 +1376,7 @@ else if (stateSchematic.isOf(Blocks.FARMLAND) && PRINTER_PRINT_DIRT_VARIANTS.get side = applyPlacementFacing(stateSchematic, sideOrig, stateClient); hitResult = new BlockHitResult(hitPos, side, npos, false); if (doSchematicWorldPickBlock(mc, stateSchematic, pos)) { - interactBlock(mc, hitResult); //double slab + BedrockBreaker.interactBlock(mc, hitResult); //double slab io.github.eatmyvenom.litematicin.utils.InventoryUtils.decrementCount(isCreative); cacheEasyPlacePosition(pos, false); sleepWhenRequired(mc); @@ -1396,7 +1393,7 @@ else if (stateSchematic.isOf(Blocks.FARMLAND) && PRINTER_PRINT_DIRT_VARIANTS.get side = applyPlacementFacing(stateSchematic, sideOrig, stateClient); hitResult = new BlockHitResult(hitPos, side, npos, false); if (doSchematicWorldPickBlock(mc, stateSchematic, pos)) { - interactBlock(mc, hitResult); //double slab + BedrockBreaker.interactBlock(mc, hitResult); //double slab io.github.eatmyvenom.litematicin.utils.InventoryUtils.decrementCount(isCreative); cacheEasyPlacePosition(pos, false); sleepWhenRequired(mc); @@ -1409,7 +1406,7 @@ else if (stateSchematic.isOf(Blocks.FARMLAND) && PRINTER_PRINT_DIRT_VARIANTS.get if (interact >= maxInteract) { if (shouldSleepLonger) { shouldSleepLonger = false; - lastPlaced = Math.max(lastPlaced, new Date().getTime() + PRINTER_SLEEP_STACK_EMPTIED.getIntegerValue()); + lastPlaced = Math.max(lastPlaced, new Date().getTime() + LitematicaMixinMod.PRINTER_SLEEP_STACK_EMPTIED.getIntegerValue()); } else { lastPlaced = Math.max(lastPlaced, new Date().getTime()); } @@ -1427,7 +1424,7 @@ else if (stateSchematic.isOf(Blocks.FARMLAND) && PRINTER_PRINT_DIRT_VARIANTS.get if (interact > 0) { if (shouldSleepLonger) { shouldSleepLonger = false; - lastPlaced = Math.max(lastPlaced, new Date().getTime() + PRINTER_SLEEP_STACK_EMPTIED.getIntegerValue()); + lastPlaced = Math.max(lastPlaced, new Date().getTime() + LitematicaMixinMod.PRINTER_SLEEP_STACK_EMPTIED.getIntegerValue()); } else { lastPlaced = Math.max(lastPlaced, new Date().getTime()); } @@ -1765,12 +1762,12 @@ else if (qcState.isSolidBlock(schematicWorld, qcPos)) { } private static void sleepWhenRequired(MinecraftClient mc) { - if (!USE_INVENTORY_CACHE.getBooleanValue()) { + if (!LitematicaMixinMod.USE_INVENTORY_CACHE.getBooleanValue()) { return; } - if (PRINTER_SLEEP_STACK_EMPTIED.getIntegerValue() > 0 && io.github.eatmyvenom.litematicin.utils.InventoryUtils.lastCount <= 0) { + if (LitematicaMixinMod.PRINTER_SLEEP_STACK_EMPTIED.getIntegerValue() > 0 && io.github.eatmyvenom.litematicin.utils.InventoryUtils.lastCount <= 0) { shouldSleepLonger = true; - lastPlaced = new Date().getTime() + PRINTER_SLEEP_STACK_EMPTIED.getIntegerValue(); + lastPlaced = new Date().getTime() + LitematicaMixinMod.PRINTER_SLEEP_STACK_EMPTIED.getIntegerValue(); MessageHolder.sendUniqueMessageActionBar(mc.player, "Sleeping because stack is emptied!"); isSleeping = true; } @@ -1778,12 +1775,12 @@ private static void sleepWhenRequired(MinecraftClient mc) { private static boolean isQCableBlock(World world, BlockPos pos) { Block block = world.getBlockState(pos).getBlock(); - return (!PRINTER_AVOID_CHECK_ONLY_PISTONS.getBooleanValue() && block instanceof DispenserBlock) || block instanceof PistonBlock; + return (!LitematicaMixinMod.PRINTER_AVOID_CHECK_ONLY_PISTONS.getBooleanValue() && block instanceof DispenserBlock) || block instanceof PistonBlock; } private static boolean isQCableBlock(BlockState blockState) { Block block = blockState.getBlock(); - return (!PRINTER_AVOID_CHECK_ONLY_PISTONS.getBooleanValue() && block instanceof DispenserBlock) || block instanceof PistonBlock; + return (!LitematicaMixinMod.PRINTER_AVOID_CHECK_ONLY_PISTONS.getBooleanValue() && block instanceof DispenserBlock) || block instanceof PistonBlock; } /*** @@ -1901,16 +1898,16 @@ private static boolean hasDustOrAscendingRails(World schematicWorld, Direction w } else if (state.getBlock() instanceof PoweredRailBlock) { switch (watching) { - case NORTH : { + case Direction.NORTH : { return state.get(PoweredRailBlock.SHAPE) == RailShape.ASCENDING_SOUTH; } - case SOUTH : { + case Direction.SOUTH : { return state.get(PoweredRailBlock.SHAPE) == RailShape.ASCENDING_NORTH; } - case EAST : { + case Direction.EAST : { return state.get(PoweredRailBlock.SHAPE) == RailShape.ASCENDING_WEST; } - case WEST : { + case Direction.WEST : { return state.get(PoweredRailBlock.SHAPE) == RailShape.ASCENDING_EAST; } default : { @@ -2026,7 +2023,7 @@ private static boolean placeCart(BlockState state, MinecraftClient client, Block return false; } if (doSchematicWorldPickBlock(client, Items.MINECART.getDefaultStack())) { - ActionResult actionResult = interactBlock(client, new BlockHitResult(clickPos, Direction.UP, pos, false)); //place block + ActionResult actionResult = BedrockBreaker.interactBlock(client, new BlockHitResult(clickPos, Direction.UP, pos, false)); //place block if (actionResult.isAccepted()) { cacheEasyPlacePosition(pos, false, 600); return true; @@ -2039,7 +2036,7 @@ private static boolean placeCart(BlockState state, MinecraftClient client, Block @SuppressWarnings({"ConstantConditions"}) private static void placeGrindStone(BlockState state, MinecraftClient client, BlockPos pos) { - if (PRINTER_FAKE_ROTATION.getBooleanValue()) { + if (LitematicaMixinMod.PRINTER_FAKE_ROTATION.getBooleanValue()) { FakeAccurateBlockPlacement.request(state, pos); return; //place in air @@ -2063,7 +2060,7 @@ private static void placeGrindStone(BlockState state, MinecraftClient client, Bl hitVec = Vec3d.ofCenter(clickPos).add(Vec3d.of(side.getVector()).multiply(0.5)); if (doSchematicWorldPickBlock(client, state, pos)) { cacheEasyPlacePosition(pos, false); - interactBlock(client, new BlockHitResult(hitVec, side, clickPos, false)); //place block + BedrockBreaker.interactBlock(client, new BlockHitResult(hitVec, side, clickPos, false)); //place block } } else { if (isFacingCorrectly(state, client.player)) { @@ -2071,7 +2068,7 @@ private static void placeGrindStone(BlockState state, MinecraftClient client, Bl clickPos = pos; if (doSchematicWorldPickBlock(client, state, pos)) { cacheEasyPlacePosition(pos, false); - interactBlock(client, new BlockHitResult(hitVec, side, clickPos, false)); //place block + BedrockBreaker.interactBlock(client, new BlockHitResult(hitVec, side, clickPos, false)); //place block } } } @@ -2079,7 +2076,7 @@ private static void placeGrindStone(BlockState state, MinecraftClient client, Bl @SuppressWarnings({"ConstantConditions"}) private static boolean canAttachGrindstone(BlockState state, MinecraftClient client, BlockPos pos) { - if (PRINTER_FAKE_ROTATION.getBooleanValue()) { + if (LitematicaMixinMod.PRINTER_FAKE_ROTATION.getBooleanValue()) { return true; //place in air. } @@ -2087,11 +2084,11 @@ private static boolean canAttachGrindstone(BlockState state, MinecraftClient cli BlockFace location = state.get(WallMountedBlock.FACE); //case ceil if (location == BlockFace.CEILING) { - return !isReplaceable(client.world.getBlockState(pos.up())) && client.player.getHorizontalFacing() == facing.getOpposite() && !hasGui(client.world.getBlockState(pos.up()).getBlock()) || client.player.shouldCancelInteraction(); + return !BedrockBreaker.isReplaceable(client.world.getBlockState(pos.up())) && client.player.getHorizontalFacing() == facing.getOpposite() && !hasGui(client.world.getBlockState(pos.up()).getBlock()) || client.player.shouldCancelInteraction(); } else if (location == BlockFace.FLOOR) { - return !isReplaceable(client.world.getBlockState(pos.down())) && client.player.getHorizontalFacing() == facing.getOpposite() && !hasGui(client.world.getBlockState(pos.down()).getBlock()) || client.player.shouldCancelInteraction(); + return !BedrockBreaker.isReplaceable(client.world.getBlockState(pos.down())) && client.player.getHorizontalFacing() == facing.getOpposite() && !hasGui(client.world.getBlockState(pos.down()).getBlock()) || client.player.shouldCancelInteraction(); } else { - return !isReplaceable(client.world.getBlockState(pos.offset(facing.getOpposite()))) && !hasGui(client.world.getBlockState(pos.offset(facing.getOpposite())).getBlock()) || client.player.shouldCancelInteraction(); + return !BedrockBreaker.isReplaceable(client.world.getBlockState(pos.offset(facing.getOpposite()))) && !hasGui(client.world.getBlockState(pos.offset(facing.getOpposite())).getBlock()) || client.player.shouldCancelInteraction(); } } @@ -2116,7 +2113,7 @@ private static void placeTrapDoor(BlockState state, MinecraftClient client, Bloc Direction side = state.get(TrapdoorBlock.FACING); BlockPos clickPos; Vec3d hitVec; - if (isReplaceable(client.world.getBlockState(pos.offset(side.getOpposite())))) { + if (BedrockBreaker.isReplaceable(client.world.getBlockState(pos.offset(side.getOpposite())))) { //place inside block clickPos = pos; if (client.player.getHorizontalFacing().getOpposite() == side) { @@ -2131,7 +2128,7 @@ private static void placeTrapDoor(BlockState state, MinecraftClient client, Bloc } if (doSchematicWorldPickBlock(client, state, pos)) { cacheEasyPlacePosition(pos, false); - interactBlock(client, new BlockHitResult(hitVec, side, clickPos, false)); //place block + BedrockBreaker.interactBlock(client, new BlockHitResult(hitVec, side, clickPos, false)); //place block } } @@ -2140,7 +2137,7 @@ private static boolean isNoteBlockInstrumentError(MinecraftClient mc, World worl BlockState stateB = mc.world.getBlockState(pos); return stateA.isOf(Blocks.NOTE_BLOCK) && stateB.isOf(Blocks.NOTE_BLOCK) && stateA.get(NoteBlock.POWERED) == stateB.get(NoteBlock.POWERED) && - isReplaceable(world.getBlockState(pos.down())) == isReplaceable(mc.world.getBlockState(pos.offset(Direction.DOWN))); + BedrockBreaker.isReplaceable(world.getBlockState(pos.down())) == BedrockBreaker.isReplaceable(mc.world.getBlockState(pos.offset(Direction.DOWN))); } private static boolean isDoorHingeError(MinecraftClient mc, World world, BlockPos pos) { @@ -2155,7 +2152,7 @@ private static boolean isDoorHingeError(MinecraftClient mc, World world, BlockPo private static boolean ObserverUpdateOrder(MinecraftClient mc, World world, BlockPos pos, Box selectedBox) { //returns true if observer should not be placed - boolean ExplicitObserver = PRINTER_OBSERVER_AVOID_ALL.getBooleanValue(); + boolean ExplicitObserver = LitematicaMixinMod.PRINTER_OBSERVER_AVOID_ALL.getBooleanValue(); BlockState stateSchematic = world.getBlockState(pos); BlockPos posOffset; BlockState OffsetStateSchematic; @@ -2200,7 +2197,7 @@ private static boolean ObserverUpdateOrder(MinecraftClient mc, World world, Bloc private static BlockPos ObserverUpdateOrderPos(MinecraftClient mc, World world, BlockPos pos) { //returns true if observer should not be placed - boolean ExplicitObserver = PRINTER_OBSERVER_AVOID_ALL.getBooleanValue(); + boolean ExplicitObserver = LitematicaMixinMod.PRINTER_OBSERVER_AVOID_ALL.getBooleanValue(); BlockState stateSchematic = world.getBlockState(pos); BlockPos posOffset; BlockState OffsetStateSchematic; @@ -2298,14 +2295,14 @@ private static boolean isReplaceableFluidSource(BlockState checkState) { return checkState.getBlock() instanceof FluidBlock && checkState.get(FluidBlock.LEVEL) == 0 || checkState.getBlock() instanceof BubbleColumnBlock || checkState.isOf(Blocks.SEAGRASS) || checkState.isOf(Blocks.TALL_SEAGRASS) || - checkState.getBlock() instanceof Waterloggable && checkState.get(Properties.WATERLOGGED) && isReplaceable(checkState); + checkState.getBlock() instanceof Waterloggable && checkState.get(Properties.WATERLOGGED) && BedrockBreaker.isReplaceable(checkState); } static boolean isReplaceableWaterFluidSource(BlockState checkState) { return checkState.isOf(Blocks.SEAGRASS) || checkState.isOf(Blocks.TALL_SEAGRASS) || checkState.isOf(Blocks.WATER) && checkState.contains(FluidBlock.LEVEL) && checkState.get(FluidBlock.LEVEL) == 0 || checkState.getBlock() instanceof BubbleColumnBlock || - checkState.getBlock() instanceof Waterloggable && checkState.contains(Properties.WATERLOGGED) && checkState.get(Properties.WATERLOGGED) && isReplaceable(checkState); + checkState.getBlock() instanceof Waterloggable && checkState.contains(Properties.WATERLOGGED) && checkState.get(Properties.WATERLOGGED) && BedrockBreaker.isReplaceable(checkState); } private static boolean containsWaterloggable(BlockState state) { @@ -2347,7 +2344,7 @@ private static boolean requiresMoreAction(BlockState stateSchematic, BlockState return true; } // finally - return !stateClient.isAir() && !isReplaceable(stateClient); + return !stateClient.isAir() && !BedrockBreaker.isReplaceable(stateClient); } /** @@ -2658,7 +2655,7 @@ static Direction applyPlacementFacing(BlockState stateSchematic, Direction side, } else if (blockSchematic instanceof LadderBlock) { return stateSchematic.get(LadderBlock.FACING); } else if (blockSchematic instanceof TrapdoorBlock) { - if (PRINTER_ACCURATE_BLOCK_PLACEMENT.getBooleanValue()) { + if (LitematicaMixinMod.PRINTER_ACCURATE_BLOCK_PLACEMENT.getBooleanValue()) { return Direction.UP; //Placement State fixing first } return stateSchematic.get(TrapdoorBlock.FACING); @@ -2667,7 +2664,7 @@ static Direction applyPlacementFacing(BlockState stateSchematic, Direction side, } else if (blockSchematic instanceof EndRodBlock) { return stateSchematic.get(EndRodBlock.FACING); } else if (blockSchematic instanceof AnvilBlock) { - if (ADVANCED_ACCURATE_BLOCK_PLACEMENT.getBooleanValue() || PRINTER_ACCURATE_BLOCK_PLACEMENT.getBooleanValue() && IsBlockSupportedCarpet(blockSchematic)) { + if (LitematicaMixinMod.ADVANCED_ACCURATE_BLOCK_PLACEMENT.getBooleanValue() || LitematicaMixinMod.PRINTER_ACCURATE_BLOCK_PLACEMENT.getBooleanValue() && IsBlockSupportedCarpet(blockSchematic)) { return stateSchematic.get(AnvilBlock.FACING); } return stateSchematic.get(AnvilBlock.FACING).rotateYCounterclockwise(); @@ -2723,7 +2720,7 @@ public static boolean isPositionCached(BlockPos pos, boolean useClicked) { } public static void cacheEasyPlacePosition(BlockPos pos, boolean useClicked) { - PositionCache item = new PositionCache(pos, System.nanoTime(), useClicked ? EASY_PLACE_CACHE_TIME.getIntegerValue() * 1000000L : 2800000000L); + PositionCache item = new PositionCache(pos, System.nanoTime(), useClicked ? LitematicaMixinMod.EASY_PLACE_CACHE_TIME.getIntegerValue() * 1000000L : 2800000000L); // TODO: Create a separate cache for clickable items, as this just makes // duplicates if (useClicked) { diff --git a/versions/1.21.0/gradle.properties b/versions/1.21.0/gradle.properties new file mode 100644 index 00000000..d09d637e --- /dev/null +++ b/versions/1.21.0/gradle.properties @@ -0,0 +1,15 @@ +# Fabric Properties + # check these on https://fabricmc.net/use + minecraft_version=1.21-pre2 + yarn_mappings=1.21-pre2+build.2 + fabricapi_version=0.99.4+1.21 + minecraft_version_out = 1.21-beta.2 + +# Mod Properties + #malilib_projectid=303119 + #malilib_fileid=4946328 + #litematica_fileid=4946471 + #litematica_projectid=308892 + essentialclient_filename=essential-client-1.20.1-1.3.6.jar + malilib_filename=malilib-fabric-1.21-pre2-0.18.999-snap.jar + litematica_filename=litematica-fabric-1.21-pre2-0.17.999-snap.jar diff --git a/versions/1.21.0/src/main/java/io/github/eatmyvenom/litematicin/mixin/Litematica/ChunkRendererSchematicVboMixin.java b/versions/1.21.0/src/main/java/io/github/eatmyvenom/litematicin/mixin/Litematica/ChunkRendererSchematicVboMixin.java new file mode 100644 index 00000000..fb1a30bd --- /dev/null +++ b/versions/1.21.0/src/main/java/io/github/eatmyvenom/litematicin/mixin/Litematica/ChunkRendererSchematicVboMixin.java @@ -0,0 +1,39 @@ +package io.github.eatmyvenom.litematicin.mixin.Litematica; + +import java.util.Set; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.client.render.RenderLayer; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.item.Item; +import net.minecraft.util.math.BlockPos; +import io.github.eatmyvenom.litematicin.LitematicaMixinMod; +import fi.dy.masa.litematica.render.schematic.ChunkCacheSchematic; +import fi.dy.masa.litematica.render.schematic.ChunkRenderDataSchematic; +import fi.dy.masa.litematica.render.schematic.ChunkRendererSchematicVbo; + +import static io.github.eatmyvenom.litematicin.utils.InventoryUtils.ITEMS; + +@Mixin(value = ChunkRendererSchematicVbo.class, priority = 1200) +public class ChunkRendererSchematicVboMixin +{ + + @Shadow + protected ChunkCacheSchematic schematicWorldView; + + @Inject(method = "renderBlocksAndOverlay", at = @At("HEAD"), cancellable = true, remap = false) + private void onRenderBlocksAndOverlay(BlockPos pos, ChunkRenderDataSchematic data, Set tileEntities, Set usedLayers, MatrixStack matrixStack, CallbackInfo ci) { + if (!LitematicaMixinMod.RENDER_ONLY_HOLDING_ITEMS.getBooleanValue()) return; + BlockState stateSchematic = this.schematicWorldView.getBlockState(pos); + Item item = stateSchematic.getBlock().asItem(); + if (!ITEMS.contains(item)) { + ci.cancel(); + } + } + +} diff --git a/versions/1.21.0/src/main/java/io/github/eatmyvenom/litematicin/mixin/Litematica/MinecraftClientMixin.java b/versions/1.21.0/src/main/java/io/github/eatmyvenom/litematicin/mixin/Litematica/MinecraftClientMixin.java new file mode 100644 index 00000000..6dd8b271 --- /dev/null +++ b/versions/1.21.0/src/main/java/io/github/eatmyvenom/litematicin/mixin/Litematica/MinecraftClientMixin.java @@ -0,0 +1,59 @@ +package io.github.eatmyvenom.litematicin.mixin.Litematica; + +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.DownloadingTerrainScreen; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.hit.HitResult; +import io.github.eatmyvenom.litematicin.utils.*; + + +@Mixin(MinecraftClient.class) +public abstract class MinecraftClientMixin +{ + @Shadow + public HitResult crosshairTarget; + + @Shadow + @Nullable + public ClientWorld world; + + @Shadow + @Nullable + public abstract ClientPlayNetworkHandler getNetworkHandler(); + + @Shadow + @Nullable + public ClientPlayerEntity player; + + // On join a new world/server + @Inject(at = @At("HEAD"), method = "joinWorld") + public void joinWorld(ClientWorld world, DownloadingTerrainScreen.WorldEntryReason worldEntryReason, CallbackInfo ci) { + Printer.worldBottomY = world.getBottomY(); + Printer.worldTopY = world.getTopY(); + } + + @Inject(at = @At("HEAD"), method = "tick") + public void onPrinterTickCount(CallbackInfo info) { + BedrockBreaker.tick(); + InventoryUtils.tick(); + FakeAccurateBlockPlacement.tick(this.getNetworkHandler(), this.player); + } + + @Inject(at = @At("HEAD"), method = "doItemUse") + public void getIfBlockEntity(CallbackInfo info) { + if (crosshairTarget != null && crosshairTarget.getType() == HitResult.Type.BLOCK && (this.world != null ? this.world.getBlockEntity(((BlockHitResult) crosshairTarget).getBlockPos()) : null) != null) { + ItemInputs.clickedPos = ((BlockHitResult) crosshairTarget).getBlockPos(); + } else { + ItemInputs.clickedPos = null; + } + } +} diff --git a/versions/1.21.0/src/main/java/io/github/eatmyvenom/litematicin/utils/Breaker.java b/versions/1.21.0/src/main/java/io/github/eatmyvenom/litematicin/utils/Breaker.java new file mode 100644 index 00000000..b0cfd34e --- /dev/null +++ b/versions/1.21.0/src/main/java/io/github/eatmyvenom/litematicin/utils/Breaker.java @@ -0,0 +1,136 @@ +package io.github.eatmyvenom.litematicin.utils; + +import java.util.Optional; +import net.minecraft.block.BlockState; +import net.minecraft.client.MinecraftClient; +import net.minecraft.enchantment.Enchantment; +import net.minecraft.enchantment.EnchantmentHelper; +import net.minecraft.enchantment.Enchantments; +import net.minecraft.item.ItemStack; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.util.Hand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import fi.dy.masa.malilib.event.TickHandler; +import fi.dy.masa.malilib.interfaces.IClientTickHandler; +import fi.dy.masa.litematica.config.Hotkeys; + +/** + * The breaking needs to be done every tick, since the WorldUtils.easyPlaceOnUseTick (which calls our Printer) + * is called multiple times per tick we cannot break blocks through that method. Or the speed will be twice the + * normal speed and detectable by anti-cheats. + */ +public class Breaker implements IClientTickHandler { + + private boolean breakingBlock = false; + private BlockPos pos; + + public Breaker() { + TickHandler.getInstance().registerClientTickHandler(this); + } + + public boolean startBreakingBlock(BlockPos pos, MinecraftClient mc) { + this.breakingBlock = true; + this.pos = pos; + // Check for best tool in inventory + if (mc.world.getBlockState(pos).getHardness(mc.world, pos) == 0) { + mc.interactionManager.attackBlock(pos, Direction.UP); + return false; + } + int bestSlotId = getBestItemSlotIdToMineBlock(mc, pos); + // If slot isn't selected, change + if (bestSlotId != -1) { + ItemStack stack = InventoryUtils.getInventory(mc.player).getStack(bestSlotId); + InventoryUtils.swapToItem(mc, stack); + } + // Start breaking + BlockState blockState = mc.world.getBlockState(pos); + //#if MC>=11800 + if (blockState.calcBlockBreakingDelta(mc.player, mc.player.getWorld(), pos) >= 1.0F) { + //#else + //$$ if (blockState.calcBlockBreakingDelta(mc.player, mc.player.world, pos) >= 1.0F) { + //#endif + mc.interactionManager.attackBlock(pos, Direction.UP); + return false; + } + TickHandler.getInstance().registerClientTickHandler(this); + return true; + } + + public boolean isBreakingBlock() { + if (this.pos == null || MinecraftClient.getInstance().world == null) { + return false; + } + if (BedrockBreaker.isReplaceable(MinecraftClient.getInstance().world.getBlockState(pos))) { + this.breakingBlock = false; + } + return this.breakingBlock; + } + + public static int getBestItemSlotIdToMineBlock(MinecraftClient mc, BlockPos blockToMine) { + int bestSlot = -1; + float bestSpeed = 0; + BlockState state = mc.world.getBlockState(blockToMine); + return getFastestToolSlot(mc, bestSlot, bestSpeed, state); + } + + private static int getFastestToolSlot(MinecraftClient mc, int bestSlot, float bestSpeed, BlockState state) { + for (int i = InventoryUtils.getInventory(mc.player).size(); i >= 0; i--) { + float speed = getBlockBreakingSpeed(state, mc, i); + if ((speed > bestSpeed && speed > 1.0F) + || (speed >= bestSpeed && !mc.player.getInventory().getStack(i).isDamageable())) { + bestSlot = i; + bestSpeed = speed; + } + } + return bestSlot; + } + + public static int getBestItemSlotIdToMineState(MinecraftClient mc, BlockState state) { + int bestSlot = -1; + float bestSpeed = 0; + return getFastestToolSlot(mc, bestSlot, bestSpeed, state); + } + + public static float getBlockBreakingSpeed(BlockState block, MinecraftClient mc, int slotId) { + if (slotId < -1 || slotId >= 36) { + return 0; + } + float f = InventoryUtils.getInventory(mc.player).main.get(slotId).getMiningSpeedMultiplier(block); + if (f > 1.0F) { + ItemStack itemStack = mc.player.getInventory().getMainHandStack(); + Optional> optional = mc.world.getRegistryManager().get(RegistryKeys.ENCHANTMENT).getEntry(Enchantments.EFFICIENCY); + int i = optional.map(enchantmentReference -> EnchantmentHelper.getLevel(enchantmentReference, itemStack)).orElse(0); + if (i > 0 && !itemStack.isEmpty()) { + f += (float) (i * i + 1); + } + } + return f; + } + + @Override + public void onClientTick(MinecraftClient mc) { + if (!isBreakingBlock() || mc.player == null) { + this.breakingBlock = false; + return; + } + + if (Hotkeys.EASY_PLACE_ACTIVATION.getKeybind().isKeybindHeld()) { // Only continue mining while the correct keys are pressed + Direction side = Direction.values()[0]; + if (mc.interactionManager.updateBlockBreakingProgress(pos, side)) { + mc.particleManager.addBlockBreakingParticles(pos, side); + mc.player.swingHand(Hand.MAIN_HAND); + } + } + + if (!BedrockBreaker.isReplaceable(mc.world.getBlockState(pos))) { + this.breakingBlock = false; + return; + } // If block isn't broken yet, dont stop + // Stop breaking + this.breakingBlock = false; + mc.interactionManager.cancelBlockBreaking(); + } + +} diff --git a/versions/1.21.0/src/main/java/io/github/eatmyvenom/litematicin/utils/InventoryUtils.java b/versions/1.21.0/src/main/java/io/github/eatmyvenom/litematicin/utils/InventoryUtils.java new file mode 100644 index 00000000..6e8420ea --- /dev/null +++ b/versions/1.21.0/src/main/java/io/github/eatmyvenom/litematicin/utils/InventoryUtils.java @@ -0,0 +1,556 @@ +package io.github.eatmyvenom.litematicin.utils; + +import fi.dy.masa.litematica.config.Configs; +import fi.dy.masa.litematica.config.Hotkeys; +import fi.dy.masa.litematica.materials.MaterialCache; +import io.github.eatmyvenom.litematicin.LitematicaMixinMod; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.block.ShulkerBoxBlock; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.LootableContainerBlockEntity; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.ContainerComponent; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.inventory.Inventory; +import net.minecraft.item.*; +import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket; +import net.minecraft.network.packet.c2s.play.UpdateSelectedSlotC2SPacket; +import net.minecraft.screen.slot.SlotActionType; +import net.minecraft.util.collection.DefaultedList; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.World; + +import java.util.*; +import java.util.function.Predicate; + +public class InventoryUtils { + private static int ptr = -1; + + public final static HashSet ITEMS = new HashSet<>(); + public static int lastCount = 0; + public static int itemChangeCount = 0; + public static Item handlingItem = null; + public static Item previousItem = null; //only used for checks + public static int trackedSelectedSlot = -1; + + private static long tickCount = 0; + private static long lastWorkedTick = 0; + + private static String cachedPickBlockableSlots = ""; + + private static final HashSet pickBlockableSlots = new HashSet<>(); + public static HashMap usedSlots = new LinkedHashMap<>(); + public static HashMap slotCounts = new LinkedHashMap<>(); + + public static void tick() { + tickCount++; + if (LitematicaMixinMod.RENDER_ONLY_HOLDING_ITEMS.getBooleanValue() && tickCount % 20 == 0) { + calculateCache(); + } + if (LitematicaMixinMod.INVENTORY_CACHE_TICKS.getIntegerValue() != 0 && tickCount - lastWorkedTick > LitematicaMixinMod.INVENTORY_CACHE_TICKS.getIntegerValue()){ + clearCache(); + } + if (!Printer.isSleeping && Configs.Generic.EASY_PLACE_MODE.getBooleanValue() && Configs.Generic.EASY_PLACE_HOLD_ENABLED.getBooleanValue() && Hotkeys.EASY_PLACE_ACTIVATION.getKeybind().isKeybindHeld()) { + for (int i = 0; i < 9; i++) { + if (!usedSlots.containsKey(i)) { + continue; + } + if (slotCounts.getOrDefault(i,0) <= 0) { + usedSlots.remove(i); + slotCounts.remove(i); + } + } + } else { + clearCache(); + } + } + + public static void clearCache(){ + if (!usedSlots.isEmpty()) MessageHolder.sendOrderMessage("Clearing cache"); + trackedSelectedSlot = -1; + previousItem = null; + handlingItem = null; + usedSlots.clear(); + slotCounts.clear(); + lastWorkedTick = tickCount; + } + + + @SuppressWarnings("PatternVariableCanBeUsed") // because for compatibility with 1.16.5 + private static void calculateCache() { + ITEMS.clear(); + MinecraftClient client = MinecraftClient.getInstance(); + if (client == null || client.player == null || client.world == null) { + return; + } + //#if MC>=11700 + for (ItemStack stack : client.player.getInventory().main) { + //#else + //$$ for (ItemStack stack : client.player.inventory.main) { + //#endif + Item item = stack.getItem(); + if (item != null) { + ITEMS.add(item); + } + if (item instanceof BlockItem) { + BlockItem blockItem = (BlockItem) item; + if (blockItem.getBlock() instanceof ShulkerBoxBlock) { + int invSize = 27; + DefaultedList returnStacks = DefaultedList.ofSize(invSize, ItemStack.EMPTY); + ContainerComponent container = stack.getComponents().get(DataComponentTypes.CONTAINER); + if (container != null) { + container.copyTo(returnStacks); + for (ItemStack returnStack : returnStacks) { + Item returnItem = returnStack.getItem(); + if (returnItem != null) { + ITEMS.add(returnItem); + } + } + } + } + } + } + } + + public static PlayerInventory getInventory(ClientPlayerEntity player) { + //#if MC>=11700 + return player.getInventory(); + //#else + //$$ return player.inventory; + //#endif + } + + public static boolean isCreative(ClientPlayerEntity player) { + //#if MC>=11700 + return player.getAbilities().creativeMode; + //#else + //$$ return player.abilities.creativeMode; + //#endif + } + + public static boolean isToolLikeItem(Item item) { + // ToolItem or FlintAndSteelItem or ShearsItem + return item instanceof ToolItem || item instanceof FlintAndSteelItem || item instanceof ShearsItem; + } + + public static void decrementCount(boolean isCreative) { + if (isCreative) { + lastCount = 65536; + slotCounts.computeIfPresent(trackedSelectedSlot, (key, value) -> 65536); + return; + } + if (lastCount > 0 && usedSlots.get(trackedSelectedSlot) != null && !isToolLikeItem(usedSlots.get(trackedSelectedSlot))) { + lastCount--; + slotCounts.computeIfPresent(trackedSelectedSlot, (key, value) -> value - 1); + } + } + private static int getPtr() { + parsePickblockableSlots(); + if (pickBlockableSlots.isEmpty()) { + return -1; + } + ptr++; + ptr = ptr % pickBlockableSlots.size(); + return ptr; + } + + private static void parsePickblockableSlots() { + String pickBlockableSlot = Configs.Generic.PICK_BLOCKABLE_SLOTS.getStringValue(); + if (!pickBlockableSlot.equals(cachedPickBlockableSlots)) { + cachedPickBlockableSlots = pickBlockableSlot; + pickBlockableSlots.clear(); + for (String s : pickBlockableSlot.split(",")) { + try { + int i = Integer.parseInt(s); + if (i>0 && i<10) { + pickBlockableSlots.add(i-1); + } + } catch (NumberFormatException e) { + // ignore + } + } + } + } + + // getAvailableSlot() is used to get the slot that the item is in, or the next available slot if it's not in the hotbar + public static int getAvailableSlot(Item item) { + if (usedSlots.containsValue(item)) { + for (Integer i : usedSlots.keySet()) { + if (usedSlots.get(i) == item) { + return i; + } + } + return -1; + } + parsePickblockableSlots(); + if (usedSlots.size() == pickBlockableSlots.size()) { //full + return getPtr(); + } + for (int i = 0; i < 9; i++) { + if (usedSlots.containsKey(i) || !pickBlockableSlots.contains(i)) { + continue; + } + return i; + } + return -1; + } + + private static int searchSlot(Item item) { + for (Integer i : usedSlots.keySet()) { + if (usedSlots.get(i) == item && slotCounts.getOrDefault(i, 0) > 0) { + return i; + } + } + return -1; + } + + public static ItemStack getMainHandStack(ClientPlayerEntity player) { + return player.getMainHandStack(); + } + + public static boolean areItemsExact(ItemStack a, ItemStack b) { + // ToolItem or FlintAndSteelItem + return exceptToolItems(a, b); + } + + public static boolean areItemsExact(ItemStack a, ItemStack b, boolean allowNamed) { + if (allowNamed) { + return areItemsExactAllowNamed(a, b); + } + return exceptToolItems(a, b); + } + + private static boolean exceptToolItems(ItemStack a, ItemStack b) { + if (isToolLikeItem(a.getItem()) || isToolLikeItem(b.getItem())) { + return a.getItem() == b.getItem(); + } + boolean isItemEqual = ItemStack.areItemsEqual(a, b); + boolean nbtCondition = LitematicaMixinMod.PRINTER_IGNORE_NBT.getBooleanValue() || ItemStack.areItemsAndComponentsEqual(a, b); + return isItemEqual && nbtCondition; + } + + public static boolean areItemsExactCount(ItemStack a, ItemStack b, boolean allowNamed) { + if (a.getCount() != b.getCount()) { + return false; + } + if (allowNamed) { + return areItemsExactAllowNamed(a, b); + } + boolean nbtCondition = LitematicaMixinMod.PRINTER_IGNORE_NBT.getBooleanValue() || ItemStack.areItemsAndComponentsEqual(a, b); + return ItemStack.areItemsEqual(a, b) && nbtCondition; + } + + public static ItemStack getStackForState(MinecraftClient client, BlockState state, World world, BlockPos pos) { + // if state is nether portal block, return FLINT_AND_STEEL + if (state.isOf(Blocks.NETHER_PORTAL)) { + if (!LitematicaMixinMod.PRINTER_LIT_PORTAL_USE_FIRECHARGE.getBooleanValue()) return Items.FLINT_AND_STEEL.getDefaultStack(); + else { + return Items.FIRE_CHARGE.getDefaultStack(); + } + } + ItemStack stack = Printer.isReplaceableWaterFluidSource(state) && LitematicaMixinMod.PRINTER_PLACE_ICE.getBooleanValue() ? Items.ICE.getDefaultStack() : MaterialCache.getInstance().getRequiredBuildItemForState(state, world, pos); + if (LitematicaMixinMod.PRINTER_PRINT_DIRT_VARIANTS.getBooleanValue() && !Printer.canPickItem(client, stack)) { + if (state.isOf(Blocks.FARMLAND)) stack = Items.DIRT.getDefaultStack(); + else if (state.isOf(Blocks.DIRT_PATH)) stack = Items.DIRT.getDefaultStack(); + } + return stack; + } + + public static boolean areItemsExactAllowNamed(ItemStack a, ItemStack b) { + // ToolItem or FlintAndSteelItem + if (isToolLikeItem(a.getItem()) || isToolLikeItem(b.getItem())) { + return a.getItem() == b.getItem(); + } + else if (a.getItem() instanceof ToolItem || b.getItem() instanceof ToolItem) { + return false; // safety + } + return ItemStack.areItemsEqual(a, b) || a.getMaxCount() == b.getMaxCount() && a.contains(DataComponentTypes.CUSTOM_NAME) && b.contains(DataComponentTypes.CUSTOM_NAME); + } + + public static boolean requiresSwap(ClientPlayerEntity player, ItemStack stack) { + int selectedSlot = getInventory(player).selectedSlot; + if (usedSlots.get(selectedSlot) != null) { + return stack.getItem() != usedSlots.get(selectedSlot) || slotCounts.getOrDefault(selectedSlot, 0) <= 0; + } + return previousItem == null || lastCount == 0 ? !areItemsExact(getMainHandStack(player), stack) : !areItemsExact(previousItem.getDefaultStack(), stack); + } + + public static boolean canSwap(ClientPlayerEntity player, ItemStack stack) { + if (isCreative(player)) { + return true; + } + int slotNum = getSlotWithStack(player, stack); + return slotNum != -1; + } + + public static int getSlotWithItem(PlayerInventory inv, ItemStack stack) { + for (int i = 0; i < inv.main.size(); i++) { + if (ItemStack.areItemsEqual(inv.getStack(i), stack)) { + return i; + } + } + return -1; + } + + public static boolean canSwap(ClientPlayerEntity player, Predicate predicate) { + if (isCreative(player)) { + return true; + } + Inventory inv = getInventory(player); + for (int i = 0; i < inv.size(); i++) { + ItemStack stack = inv.getStack(i); + if (stack.getItem() instanceof ToolItem && predicate.test(stack)) { + return true; + } + } + return false; + } + + synchronized public static boolean swapToItem(MinecraftClient client, ItemStack stack) { + MessageHolder.sendOrderMessage("Trying to swap item into " + stack.getItem()); + ClientPlayerEntity player = client.player; + int maxChange = LitematicaMixinMod.PRINTER_MAX_ITEM_CHANGES.getIntegerValue(); + if (player == null || client.interactionManager == null) { + MessageHolder.sendOrderMessage("Player or interaction manager was null"); + return false; + } + //getInventory(player).updateItems(); + if (stack.getItem() != handlingItem) { + if (maxChange != 0 && itemChangeCount > maxChange) { + MessageHolder.sendOrderMessage("Exceeded item change count"); + return false; + } + } + if (!requiresSwap(player, stack)) { + assert trackedSelectedSlot == -1 || trackedSelectedSlot == getInventory(player).selectedSlot : + "Selected slot changed for external reason! : expected " + trackedSelectedSlot + ", current " + getInventory(player).selectedSlot; + assert previousItem == null || previousItem == stack.getItem() : "Handling item : " + handlingItem + " was not equal to " + stack.getItem(); + MessageHolder.sendOrderMessage("Didn't require swap for item " + stack.getItem() + " previous handling item : " + previousItem); + lastCount = isCreative(player) ? 65536 : getMainHandStack(player).getCount(); + if (usedSlots.containsValue(stack.getItem())) { + if (searchSlot(stack.getItem()) != trackedSelectedSlot) { + MessageHolder.sendMessageUncheckedUnique("Hotbar has duplicate item references, which should not happen!"); + } + } + trackedSelectedSlot = getInventory(player).selectedSlot; + usedSlots.put(getInventory(player).selectedSlot, getMainHandStack(player).getItem()); + slotCounts.put(getInventory(player).selectedSlot, lastCount); + previousItem = stack.getItem(); + lastWorkedTick = tickCount; + return true; + } + if (usedSlots.containsValue(stack.getItem())) { + int slot = searchSlot(stack.getItem()); + if (slot != -1) { + getInventory(player).selectedSlot = slot; + trackedSelectedSlot = getInventory(player).selectedSlot; + usedSlots.put(trackedSelectedSlot, stack.getItem()); + slotCounts.put(trackedSelectedSlot, stack.getCount()); + lastCount = stack.getCount(); + previousItem = stack.getItem(); + handlingItem = previousItem; + lastWorkedTick = tickCount; + MessageHolder.sendOrderMessage("Selected slot " + getInventory(player).selectedSlot + " based on cache for " + stack.getItem()); + client.getNetworkHandler().sendPacket(new UpdateSelectedSlotC2SPacket(getInventory(player).selectedSlot)); + return !getInventory(player).getMainHandStack().isEmpty(); + } + else { + MessageHolder.sendOrderMessage("Used slot contains stack but cannot find " + stack.getItem()); + } + } + MessageHolder.sendOrderMessage("Trying survival Swap"); + if (survivalSwap(client, player, stack)) { + usedSlots.put(getInventory(player).selectedSlot, stack.getItem()); + slotCounts.put(trackedSelectedSlot, getMainHandStack(player).getCount()); + MessageHolder.sendOrderMessage("Swapped to item " + stack.getItem()); + handlingItem = stack.getItem(); + previousItem = handlingItem; + itemChangeCount++; + lastWorkedTick = tickCount; + return true; + } + MessageHolder.sendOrderMessage("Survival swap failed, trying creative swap"); + return creativeSwap(client, player, stack); + } + + synchronized public static ItemStack findItem(MinecraftClient client, Predicate predicate) { + ClientPlayerEntity player = client.player; + if (player == null) { + return ItemStack.EMPTY; + } + Inventory inv = getInventory(player); + for (int i = 0; i < inv.size(); i++) { + ItemStack stack = inv.getStack(i); + if (stack.getItem() instanceof ToolItem && predicate.test(stack)) { + return stack; + } + } + return ItemStack.EMPTY; + } + + synchronized public static boolean swapToItem(MinecraftClient client, Predicate predicate) { + ItemStack stack = findItem(client, predicate); + return swapToItem(client, stack); + } + + public static int getSlotWithStack(ClientPlayerEntity player, ItemStack stack) { + PlayerInventory inv = getInventory(player); + return stack.getItem() instanceof ToolItem || isToolLikeItem(stack.getItem()) ? getSlotWithItem(inv, stack) :getSlotWIthStackIgnoreNbt(getInventory(player), stack); + } + + public static void printAllItems(PlayerInventory inv, ItemStack stack) { + // Debug + for (int i = 0; i < inv.main.size(); i++) { + boolean areNbtsEqual = ItemStack.areItemsAndComponentsEqual(inv.getStack(i), stack); + boolean areItemsEqual = ItemStack.areItemsEqual(inv.getStack(i), stack); + MessageHolder.sendUniqueDebugMessage("Slot " + i + ", " + inv.getStack(i).getItem() + " : " + areItemsEqual + " : " + areNbtsEqual); + } + } + + public static int getSlotWIthStackIgnoreNbt(PlayerInventory inv, ItemStack stack) { + // Debug + //printAllItems(inv, stack); + int defaultSlot = inv.getSlotWithStack(stack); + if (defaultSlot != -1) { + return defaultSlot; + } + if (!LitematicaMixinMod.PRINTER_IGNORE_NBT.getBooleanValue()) { + return defaultSlot; + } + for (int i = 0; i < inv.main.size(); i++) { + boolean areItemsEqual = ItemStack.areItemsEqual(inv.getStack(i), stack); + if (areItemsEqual) { + return i; + } + } + return -1; + } + + public static int getSlotWithStack(PlayerInventory inv, ItemStack stack) { + int findingStack = getSlotWIthStackIgnoreNbt(inv, stack); + return stack.getItem() instanceof ToolItem || isToolLikeItem(stack.getItem()) ? getSlotWithItem(inv, stack) :findingStack; + } + + @SuppressWarnings("ConstantConditions") + private static boolean creativeSwap(MinecraftClient client, ClientPlayerEntity player, ItemStack stack) { + if (!isCreative(player)) { + MessageHolder.sendOrderMessage("Player is not in creative mode"); + return false; + } + int selectedSlot = getAvailableSlot(stack.getItem()); + if (selectedSlot == -1) { + MessageHolder.sendOrderMessage("No available slot for " + stack.getItem()); + return false; + } + MessageHolder.sendOrderMessage("Clicked creative stack " + stack.getItem() + " for slot " + selectedSlot); + //getInventory(player).addPickBlock(stack); + getInventory(player).selectedSlot = selectedSlot; + client.interactionManager.clickCreativeStack(stack, 36 + selectedSlot); + client.getNetworkHandler().sendPacket(new UpdateSelectedSlotC2SPacket(getInventory(player).selectedSlot)); + trackedSelectedSlot = selectedSlot; + getInventory(player).main.set(selectedSlot, stack); + usedSlots.put(getInventory(player).selectedSlot, stack.getItem()); + slotCounts.put(trackedSelectedSlot, 65536); + lastCount = 65536; + handlingItem = stack.getItem(); + previousItem = handlingItem; + itemChangeCount++; + lastWorkedTick = tickCount; + return true; + } + + @SuppressWarnings("ConstantConditions") + private static boolean survivalSwap(MinecraftClient client, ClientPlayerEntity player, ItemStack stack) { + if (!canSwap(player, stack)) { + return false; + } + if (areItemsExact(player.getOffHandStack(), stack) && !areItemsExact(getMainHandStack(player), stack)) { + lastCount = isCreative(player) ? 65536 : client.player.getOffHandStack().getCount(); + client.getNetworkHandler().sendPacket(new PlayerActionC2SPacket(PlayerActionC2SPacket.Action.SWAP_ITEM_WITH_OFFHAND, BlockPos.ORIGIN, Direction.DOWN)); + return true; + } + int slot = getSlotWithStack(player, stack); + if (slot == -1) { + return false; + } + if (PlayerInventory.isValidHotbarIndex(slot)) { + if (usedSlots.get(slot) != null) { + MessageHolder.sendOrderMessage("Hotbar slot should have been handled before, so it must be error!"); + MessageHolder.sendOrderMessage("Expected : " + usedSlots.get(slot) + " but current client handles : " + stack.getItem()); + return false; + } + getInventory(player).selectedSlot = slot; + trackedSelectedSlot = slot; + MessageHolder.sendOrderMessage("Selected hotbar Slot " + slot); + lastCount = isCreative(player) ? 65536 : getInventory(player).getStack(slot).getCount(); + client.getNetworkHandler().sendPacket(new UpdateSelectedSlotC2SPacket(slot)); + } else { + int selectedSlot = getAvailableSlot(stack.getItem()); + if (selectedSlot == -1) { + MessageHolder.sendOrderMessage("All hotbar slots are used"); + return false; + } + lastCount = isCreative(player) ? 65536 : getInventory(player).getStack(slot).getCount(); + MessageHolder.sendOrderMessage("Slot at " + slot + "(" + getInventory(player).getStack(slot).getItem() + ")" + " is swapped with " + selectedSlot + "(" + getInventory(player).main.get(selectedSlot) + ")"); + usedSlots.put(selectedSlot, stack.getItem()); + client.interactionManager.clickSlot(player.playerScreenHandler.syncId, slot, selectedSlot, SlotActionType.SWAP, player); + getInventory(player).selectedSlot = selectedSlot; + trackedSelectedSlot = selectedSlot; + + } + try { + assert ItemStack.areEqual(getMainHandStack(player), stack); + } catch (Exception e) { + MessageHolder.sendMessageUncheckedUnique(player, stack.toString() + " does not match with " + player.getMainHandStack().toString() + "!"); + } + return true; + } + + public static List getRequiredStackInSchematic(World schematicWorld, MinecraftClient minecraftClient, BlockPos pos) { + final ClientPlayerEntity player = minecraftClient.player; + List result = new ArrayList<>(); + BlockEntity blockEntity = schematicWorld.getBlockEntity(pos); + if (blockEntity == null) { + return result; + } + if (blockEntity instanceof LootableContainerBlockEntity) { + // might use JAVA 8 + LootableContainerBlockEntity containerBlockEntity = (LootableContainerBlockEntity) blockEntity; + if (containerBlockEntity.isEmpty()) { + return result; + } + if (containerBlockEntity.canPlayerUse(player)) { + for (int i = 0; i < (containerBlockEntity).size(); i++) { + result.add(containerBlockEntity.getStack(i)); + } + } else { + MessageHolder.sendMessageUncheckedUnique(player, "Container at " + pos.toShortString() + "can't be opened by player!"); + } + } + return result; + } + + public static boolean hasItemInSchematic(World schematicWorld, BlockPos pos) { + BlockEntity blockEntity = schematicWorld.getBlockEntity(pos); + if (blockEntity == null) { + return false; + } + if (blockEntity instanceof LootableContainerBlockEntity) { + // might use JAVA 8 + LootableContainerBlockEntity containerBlockEntity = (LootableContainerBlockEntity) blockEntity; + if (containerBlockEntity.isEmpty()) { + return false; + } + for (int i = 0; i < (containerBlockEntity).size(); i++) { + if (!containerBlockEntity.getStack(i).isEmpty()) { + return true; + } + } + } + return false; + } +} \ No newline at end of file diff --git a/versions/1.21.0/src/main/java/io/github/eatmyvenom/litematicin/utils/ItemInputs.java b/versions/1.21.0/src/main/java/io/github/eatmyvenom/litematicin/utils/ItemInputs.java new file mode 100644 index 00000000..42c4bcce --- /dev/null +++ b/versions/1.21.0/src/main/java/io/github/eatmyvenom/litematicin/utils/ItemInputs.java @@ -0,0 +1,326 @@ +package io.github.eatmyvenom.litematicin.utils; + +import fi.dy.masa.litematica.world.SchematicWorldHandler; +import io.github.eatmyvenom.litematicin.LitematicaMixinMod; +import net.minecraft.block.*; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.ingame.Generic3x3ContainerScreen; +import net.minecraft.client.gui.screen.ingame.GenericContainerScreen; +import net.minecraft.client.gui.screen.ingame.HopperScreen; +import net.minecraft.client.gui.screen.ingame.InventoryScreen; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.ScreenHandler; +import net.minecraft.screen.slot.Slot; +import net.minecraft.screen.slot.SlotActionType; +import net.minecraft.util.math.BlockPos; + +import java.util.*; + +@SuppressWarnings({"ConstantConditions", "unused"}) +public +class ItemInputs +{ + + private static long handling = new Date().getTime(); + private static final HashSet handledPos = new HashSet<>(); + private static Map.Entry entry; + public static BlockPos clickedPos = null; + + public static void clear() { + handledPos.clear(); + } + + public static boolean canHandle() { + return new Date().getTime() > handling + LitematicaMixinMod.PRINTER_INVENTORY_SCREEN_WAIT.getIntegerValue(); + } + + private static void handle() { + handling = new Date().getTime(); + } + + /*** + @param player : player Entity + @param required : List of stacks, might be empty to return false + @return boolean : should process or not + ***/ + public static boolean matchStacks(List required, List current, ClientPlayerEntity player, boolean allowNamed) { + if (required.isEmpty()) { + return false; + } + List copy = new ArrayList<>(); + for (int i = 0; i < required.size(); i++) { + ItemStack copiedStack = required.get(i).copy(); + copiedStack.decrement(current.get(i).getStack().getCount()); + copy.add(copiedStack); + } + int[] countArray = player.getInventory().main.stream().mapToInt(ItemStack::getCount).toArray(); + for (ItemStack itemStack : copy) { + if (itemStack.isEmpty()) { + continue; + } + //MessageHolder.sendUniqueDebugMessage("Checking for stack " + itemStack); + int requiredAmount = itemStack.getCount(); + int i = 0; + for (ItemStack playerStacks : player.getInventory().main) { + if (countArray[i] <= 0) { + i++; + continue; + } + if (InventoryUtils.areItemsExact(itemStack, playerStacks, allowNamed)) { + //MessageHolder.sendUniqueDebugMessage("Found stack " + itemStack + " with count " + countArray[i] + " at slot num" + i + " when remaining " + requiredAmount); + if (countArray[i] >= requiredAmount) { + countArray[i] -= requiredAmount; + requiredAmount = 0; + } else { + requiredAmount -= countArray[i]; + countArray[i] = 0; + } + } + //MessageHolder.sendUniqueDebugMessage("Item count array is " + Arrays.toString(countArray)); + if (requiredAmount <= 0) { + break; + } + i++; + } + if (requiredAmount > 0) { + return false; + } + } + return true; + } + + private static int getPreference(MinecraftClient client, ScreenHandler screen, ItemStack itemStack, boolean allowNamed) { + if (screen == null || itemStack.isEmpty()) { + return -1; + } + for (int i = 0; i < screen.slots.size(); i++) { + if (!(screen.getSlot(i).inventory instanceof PlayerInventory)) { + continue; + } + ItemStack playerStacks = screen.getSlot(i).getStack(); + if (InventoryUtils.areItemsExact(itemStack, playerStacks, allowNamed)) { + return i; + } + } + return -1; + } + + private static List getNonPlayerSlots(MinecraftClient client, ScreenHandler screen) { + List retVal = new ArrayList<>(); + for (int i = 0; i < screen.slots.size(); i++) { + if ((screen.getSlot(i).inventory instanceof PlayerInventory)) { + continue; + } + retVal.add(screen.getSlot(i)); + } + return retVal; + } + + private static void clearCursor(MinecraftClient client) { + final ClientPlayerEntity player = client.player; + if (player.currentScreenHandler != null && !player.currentScreenHandler.getCursorStack().isEmpty()) { + ItemStack cursorStack = player.currentScreenHandler.getCursorStack(); + ScreenHandler handler = player.currentScreenHandler; + for (int i = 0; i < handler.slots.size(); i++) { + if (ItemStack.areItemsAndComponentsEqual(cursorStack, handler.getSlot(i).getStack())) { + client.interactionManager.clickSlot(handler.syncId, handler.getSlot(i).id, 0, SlotActionType.PICKUP, player); + return; + } + } + client.interactionManager.clickSlot(handler.syncId, -999, 1, SlotActionType.THROW, player); + } + } + + public static void execute(MinecraftClient client) { + if (canHandle()) { + handle(); + } else { + MessageHolder.sendUniqueMessageActionBar(client.player, "Cooldown...."); + return; + } + boolean allowNamed = LitematicaMixinMod.PRINTER_INVENTORY_OPERATION_ALLOW_ALL_NAMED.getBooleanValue(); + BlockPos where = rayCast(client); + if (where == null) { + MessageHolder.sendUniqueMessageActionBar(client.player, "Failed to raycast"); + return; + } + if (handledPos.contains(where.asLong())) { + MessageHolder.sendUniqueMessageActionBar(client.player, "Position is already handled"); + clickedPos = null; + client.player.closeHandledScreen(); + return; + } + if (client.player.currentScreenHandler == client.player.playerScreenHandler) { + MessageHolder.sendUniqueMessageActionBar(client.player, "Screen is not extra screen"); + return; + } + client.player.currentScreenHandler.enableSyncing(); + client.player.currentScreenHandler.syncState(); + List requiredStacks = getRaycastRequiredItemStacks(client); + if (requiredStacks.isEmpty()) { + MessageHolder.sendUniqueDebugMessage("required stacks were empty for " + where.toShortString()); + client.player.closeHandledScreen(); + return; + } + MessageHolder.sendUniqueDebugMessage("Handled pos " + where.toShortString()); + List nonPlayerSlot = getNonPlayerSlots(client, client.player.currentScreenHandler); + if (matchStacks(requiredStacks, nonPlayerSlot, client.player, allowNamed)) { + //MessageHolder.sendUniqueDebugMessage("Required pos " + where.toShortString()); + + //MessageHolder.sendUniqueDebugMessage(nonPlayerSlot.toString()); + if (requiredStacks.size() != nonPlayerSlot.size()) { + MessageHolder.sendMessageUncheckedUnique(client.player, "Sizes differ as " + requiredStacks.size() + " but non-player slot size : " + nonPlayerSlot.size()); + return; + } + if (entry == null || entry.getKey() != where.asLong()) { + entry = Map.entry(where.asLong(), new Date().getTime() + LitematicaMixinMod.PRINTER_INVENTORY_SCREEN_WAIT.getIntegerValue()); + return; + } else if (entry.getValue() > new Date().getTime()) { + return; + } else { + entry = null; + } + boolean allCorrect = true; + for (int j = 0; j < LitematicaMixinMod.PRINTER_INVENTORY_OPERATIONS_RETRY.getIntegerValue(); j++) { + for (int i = 0; i < requiredStacks.size(); i++) { + if (InventoryUtils.areItemsExactCount(nonPlayerSlot.get(i).getStack(), requiredStacks.get(i), allowNamed)) { + continue; + } + sendItem(client, nonPlayerSlot.get(i), requiredStacks.get(i), allowNamed); + if (!InventoryUtils.areItemsExactCount(nonPlayerSlot.get(i).getStack(), requiredStacks.get(i), allowNamed)) { + allCorrect = false; + } + } + } + if (allCorrect) { + handledPos.add(where.asLong()); + MessageHolder.sendUniqueDebugMessage("Successfully done operation at " + where.toShortString()); + if (LitematicaMixinMod.PRINTER_INVENTORY_OPERATIONS_CLOSE_SCREEN.getBooleanValue()) { + client.player.closeHandledScreen(); + } + } else { + MessageHolder.sendUniqueDebugMessage("Partially failed to send all items at " + where.toShortString() + ", will retry"); + } + } else { + MessageHolder.sendUniqueDebugMessage("Does not have enough item for " + where.toShortString() + "!"); + if (LitematicaMixinMod.PRINTER_INVENTORY_OPERATIONS_CLOSE_SCREEN.getBooleanValue()) { + client.player.closeHandledScreen(); + } + } + } + + /*** + * Part of the execution + * @param client : MinecraftClient + * @param targetSlot : Integer of slot defined as slot.id + * @param stack : Wanted slot to send + * @param allowNamed : allows Named item to go instead + */ + private static void sendItem(MinecraftClient client, int targetSlot, ItemStack stack, boolean allowNamed) { + clearCursor(client); + clearUnmatchTargetSlot(client, targetSlot, stack, allowNamed); + int holding = getPreference(client, client.player.currentScreenHandler, stack, allowNamed); + if (holding == -1) { + return; + } //actually we can do this + ScreenHandler screenHandler = client.player.currentScreenHandler; + leftClickSlot(screenHandler, holding); + for (int i = 0; i < stack.getCount() - screenHandler.getSlot(targetSlot).getStack().getCount(); i++) { + rightClickSlot(screenHandler, targetSlot); + } + leftClickSlot(screenHandler, holding); + MessageHolder.sendUniqueDebugMessage("Sent item from " + holding + " to " + targetSlot); + //client.interactionManager.clickSlot(client.player.currentScreenHandler.syncId, targetSlot, holding, SlotActionType.SWAP, client.player); + } + + private static void sendItem(MinecraftClient client, Slot targetSlot, ItemStack stack, boolean allowNamed) { + sendItem(client, targetSlot.id, stack, allowNamed); + } + + private static void clearUnmatchTargetSlot(MinecraftClient client, int targetSlot, ItemStack wantedItem, boolean allowNamed) { + ItemStack slotStack = client.player.currentScreenHandler.getSlot(targetSlot).getStack(); + if (InventoryUtils.areItemsExact(slotStack, wantedItem, allowNamed) && slotStack.getCount() <= wantedItem.getCount()) { + return; + } + //else we need to clear + ScreenHandler gui = client.player.currentScreenHandler; + leftClickSlot(gui, targetSlot); + clearCursor(client); + } + + private static void leftClickSlot(ScreenHandler gui, int slotNum) { + clickSlot(gui, slotNum, 0, SlotActionType.PICKUP); + } + + private static void rightClickSlot(ScreenHandler gui, int slotNum) { + clickSlot(gui, slotNum, 1, SlotActionType.PICKUP); + } + + private static void shiftClickSlot(ScreenHandler gui, int slotNum) { + clickSlot(gui, slotNum, 0, SlotActionType.QUICK_MOVE); + } + + public static void clickSlot(ScreenHandler gui, int slotNum, int button, SlotActionType action) { + if (slotNum >= 0 && slotNum < gui.slots.size()) { + Slot slot = gui.getSlot(slotNum); + clickSlot(gui, slot, button, action); + } + } + + public static void clickSlot(ScreenHandler gui, Slot slot, int button, SlotActionType action) { + try { + MinecraftClient.getInstance().interactionManager.clickSlot(gui.syncId, slot.id, button, action, MinecraftClient.getInstance().player); + } catch (Exception e) { + MessageHolder.sendMessageUncheckedUnique(MinecraftClient.getInstance().player, "Clicking slot failed "); + MessageHolder.sendMessageUncheckedUnique(MinecraftClient.getInstance().player, e.getMessage()); + } + } + + public static List getRaycastRequiredItemStacks(MinecraftClient minecraftClient) { + List retVal = new ArrayList<>(); + Screen screen = minecraftClient.currentScreen; + if (screen == null) { + return retVal; + } + if (screen instanceof InventoryScreen) { + MessageHolder.sendUniqueDebugMessage(minecraftClient.player, "Screen was InventoryScreen"); + return retVal; + } + BlockPos context = rayCast(minecraftClient); + if (context == null) { + return retVal; + } + if (handledPos.contains(context.asLong())) { + MessageHolder.sendUniqueDebugMessage(minecraftClient.player, "Screen was already registered"); + return retVal; + } + if (context == null) { + return retVal; + } + if (!(screen instanceof GenericContainerScreen || screen instanceof Generic3x3ContainerScreen || screen instanceof HopperScreen)) { + return retVal; + } + return InventoryUtils.getRequiredStackInSchematic(SchematicWorldHandler.getSchematicWorld(), minecraftClient, context); + } + + private static BlockPos rayCast(MinecraftClient minecraftClient) { + if (clickedPos == null) { + MessageHolder.sendUniqueMessageActionBar(minecraftClient.player, "Current raycast is set to null"); + return null; + } + MessageHolder.sendUniqueMessageActionBar(minecraftClient.player, "Current raycast is set to " + clickedPos.toShortString()); + BlockPos castedPos = clickedPos; + Block block = minecraftClient.world.getBlockState(castedPos).getBlock(); + if (block instanceof BlockWithEntity) { + if (block instanceof HopperBlock || block instanceof ChestBlock || block instanceof DispenserBlock) { + return castedPos; + } + } + return null; + } + + +} \ No newline at end of file diff --git a/versions/1.21.0/src/main/java/io/github/eatmyvenom/litematicin/utils/Printer.java b/versions/1.21.0/src/main/java/io/github/eatmyvenom/litematicin/utils/Printer.java new file mode 100644 index 00000000..3c5ada47 --- /dev/null +++ b/versions/1.21.0/src/main/java/io/github/eatmyvenom/litematicin/utils/Printer.java @@ -0,0 +1,2843 @@ +package io.github.eatmyvenom.litematicin.utils; + +import com.google.common.collect.ImmutableMap; +import fi.dy.masa.litematica.config.Configs; +import fi.dy.masa.litematica.data.DataManager; +import fi.dy.masa.litematica.materials.MaterialCache; +import fi.dy.masa.litematica.schematic.placement.SchematicPlacementManager.PlacementPart; +import fi.dy.masa.litematica.schematic.placement.SubRegionPlacement.RequiredEnabled; +import fi.dy.masa.litematica.selection.Box; +import fi.dy.masa.litematica.util.InventoryUtils; +import fi.dy.masa.litematica.util.*; +import fi.dy.masa.litematica.util.RayTraceUtils.RayTraceWrapper; +import fi.dy.masa.litematica.world.SchematicWorldHandler; +import fi.dy.masa.malilib.util.IntBoundingBox; +import fi.dy.masa.malilib.util.LayerRange; +import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.block.*; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.SignBlockEntity; +import net.minecraft.block.enums.*; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.ingame.SignEditScreen; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.fluid.LavaFluid; +import net.minecraft.item.*; +import net.minecraft.network.packet.c2s.play.UpdateSignC2SPacket; +import net.minecraft.registry.tag.BlockTags; +import net.minecraft.state.property.Properties; +import net.minecraft.text.Text; +//#if MC>=11900 +import net.minecraft.text.PlainTextContent; +//#else +//$$ import net.minecraft.text.LiteralText; +//#endif +//#if MC<11902 +//$$ import fi.dy.masa.malilib.util.SubChunkPos; +//#endif +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.Pair; +import net.minecraft.util.hit.BlockHitResult; +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; +import net.minecraft.world.dimension.NetherPortal; + +import java.util.*; +import java.util.function.Predicate; + +import io.github.eatmyvenom.litematicin.LitematicaMixinMod; +import static io.github.eatmyvenom.litematicin.utils.InventoryUtils.*; + +@SuppressWarnings("ConstantConditions") +public class Printer { + + private static final HashSet signCache = new HashSet<>(); + private static final LinkedHashMap, PositionCache> positionCache = new LinkedHashMap<>(); + private static Box CURRENT_BOX = null; + // For printing delay + public static boolean isSleeping = false; + public static long lastPlaced = new Date().getTime(); + private static boolean shouldSleepLonger = false; + public static Breaker breaker = new Breaker(); + public static int worldBottomY = 0; // this is handled in MinecraftClientMixin.joinWorld callback + public static int worldTopY = 256; + private static final LinkedHashMap causeMap = new LinkedHashMap<>(); + private static final Long2LongOpenHashMap referenceMap = new Long2LongOpenHashMap(); + + + // TODO: This must be moved to another class and not be static. + // Simulates and returns if player can place block as wanted. + private static boolean simulateFacingData(BlockState state, BlockPos blockPos, Vec3d hitVec) { + if (!state.getProperties().contains(Properties.FACING) && !state.getProperties().contains(Properties.HORIZONTAL_FACING)) { + return true; + } + //blocks that does not require facing + if (state.isOf(Blocks.HOPPER) || state.isIn(BlockTags.SHULKER_BOXES)) { + return true; + } + // int 0 : none, 1 : clockwise, 2 : counterclockwise, 3 : reverse + ClientPlayerEntity player = MinecraftClient.getInstance().player; + BlockHitResult hitResult = new BlockHitResult(hitVec, Direction.NORTH, blockPos, false); + Block block = state.getBlock(); + ItemPlacementContext ctx = new ItemPlacementContext(player, Hand.MAIN_HAND, state.getBlock().asItem().getDefaultStack(), hitResult); + BlockState testState; + try { + testState = block.getPlacementState(ctx); + } catch (Exception e) { //doors wtf + MessageHolder.sendMessageUncheckedUnique("Cannot get tested orientation of given block "+ state.getBlock().getName()); + //fallback to player horizontal facing... + return player.getHorizontalFacing() == fi.dy.masa.malilib.util.BlockUtils.getFirstPropertyFacingValue(state); + } + if (testState == null) { + MessageHolder.sendMessageUncheckedUnique("Cannot get tested orientation of given block "+ state.getBlock().getName()); + return player.getHorizontalFacing() == fi.dy.masa.malilib.util.BlockUtils.getFirstPropertyFacingValue(state); + } + Direction testFacing = fi.dy.masa.malilib.util.BlockUtils.getFirstPropertyFacingValue(testState); + return testFacing == fi.dy.masa.malilib.util.BlockUtils.getFirstPropertyFacingValue(state); + } + + public static boolean canPickBlock(MinecraftClient mc, BlockState preference, BlockPos pos) { + World world = SchematicWorldHandler.getSchematicWorld(); + ItemStack stack = getStackForState(mc, preference, world, pos); + if (stack.isEmpty()) { + MessageHolder.sendDebugMessage(mc.player, "Cannot pick block " + preference.getBlock().getName() + " at " + pos.toShortString() + " because no stack"); + return false; + } + // Inventory Cache + if (LitematicaMixinMod.USE_INVENTORY_CACHE.getBooleanValue() && !ITEMS.isEmpty()) { // if cache is enabled and cache is not empty + return io.github.eatmyvenom.litematicin.utils.InventoryUtils.swapToItem(mc, stack); + } + if (!stack.isEmpty() && stack.getItem() != Items.AIR) { + PlayerInventory inv = getInventory(mc.player); + if (!isCreative(mc.player)) { + if (stack.getItem() instanceof ToolItem || stack.getItem() instanceof FlintAndSteelItem) { + // manually search through inventories + int slot = io.github.eatmyvenom.litematicin.utils.InventoryUtils.getSlotWithItem(inv, stack); + if (slot == -1) { + MessageHolder.sendDebugMessage(mc.player, "Cannot pick block " + preference.getBlock().getName() + " at " + pos.toShortString() + " because no slot"); + return false; + } + if (LitematicaMixinMod.EASY_PLACE_MODE_HOTBAR_ONLY.getBooleanValue()) { + boolean isHotbar = slot < 9; + if (!isHotbar) { + MessageHolder.sendDebugMessage(mc.player, "Cannot pick block " + preference.getBlock().getName() + " at " + pos.toShortString() + " because not in hotbar"); + } + return isHotbar; + } + return true; + } + else { + int slot = getSlotWithStack(inv, stack); + if (slot == -1) { + MessageHolder.sendDebugMessage(mc.player, "Cannot pick block " + preference.getBlock().getName() + " at " + pos.toShortString() + " because no slot"); + return false; + } + if (LitematicaMixinMod.EASY_PLACE_MODE_HOTBAR_ONLY.getBooleanValue()) { + boolean isHotbar = slot < 9; + if (!isHotbar) { + MessageHolder.sendDebugMessage(mc.player, "Cannot pick block " + preference.getBlock().getName() + " at " + pos.toShortString() + " because not in hotbar"); + } + return isHotbar; + } + } + } + return true; + } + return true; + } + + public static boolean canPickItem(MinecraftClient mc, ItemStack stack) { + if (!stack.isEmpty()) { + PlayerInventory inv = getInventory(mc.player); + if (!isCreative(mc.player)) { + int slot = getSlotWithStack(inv, stack); + if (slot == -1) { + return false; + } + if (LitematicaMixinMod.EASY_PLACE_MODE_HOTBAR_ONLY.getBooleanValue()) { + return slot < 9; + } + } + return true; + } + return false; + } + + /** + * New doSchematicWorldPickBlock that allows you to choose which block you want + */ + @Environment(EnvType.CLIENT) + synchronized public static boolean doSchematicWorldPickBlock(MinecraftClient mc, BlockState preference, + BlockPos pos) { + World world = SchematicWorldHandler.getSchematicWorld(); + ItemStack stack = getStackForState(mc, preference, world, pos); + if (!FakeAccurateBlockPlacement.canHandleOther(stack.getItem())) { + MessageHolder.sendOrderMessage("Cannot handle block " + stack.getItem().getName() + ", handling other"); + return false; + } + if (!stack.isEmpty()) { + if (LitematicaMixinMod.USE_INVENTORY_CACHE.getBooleanValue()) { + boolean swapResult = io.github.eatmyvenom.litematicin.utils.InventoryUtils.swapToItem(mc, stack); + MessageHolder.sendDebugMessage(mc.player, "Swapped to " + stack.getItem().getName() + " at " + pos.toShortString() + " with result " + swapResult); + return swapResult; + } else { + InventoryUtils.schematicWorldPickBlock(stack, pos, world, mc); + //#if MC>11650 + return mc.player.getMainHandStack().isOf(stack.getItem()); + //#else + //$$ return mc.player.getMainHandStack().isItemEqual(stack); + //#endif + } + } + return false; + } + + @Environment(EnvType.CLIENT) + synchronized public static boolean doSchematicWorldPickBlock(MinecraftClient mc, ItemStack stack) { + if (!FakeAccurateBlockPlacement.canHandleOther(stack.getItem())) { + MessageHolder.sendOrderMessage("Cannot handle block " + stack.getItem().getName() + ", handling other"); + return false; + } + if (!stack.isEmpty()) { + if (LitematicaMixinMod.USE_INVENTORY_CACHE.getBooleanValue()) { + return io.github.eatmyvenom.litematicin.utils.InventoryUtils.swapToItem(mc, stack); + } else { + fi.dy.masa.malilib.util.InventoryUtils.swapItemToMainHand(stack, mc); + //#if MC>11650 + return mc.player.getMainHandStack().isOf(stack.getItem()); + //#else + //$$ return mc.player.getMainHandStack().isItemEqual(stack); + //#endif + } + } + return false; + } + + @Environment(EnvType.CLIENT) + synchronized public static boolean doSchematicWorldPickBlock(MinecraftClient mc, Predicate stack) { + ItemStack stack1 = io.github.eatmyvenom.litematicin.utils.InventoryUtils.findItem(mc, stack); + return doSchematicWorldPickBlock(mc, stack1); + } + + public static ActionResult doEasyPlaceFakeRotation(MinecraftClient mc) { //force normal easyplace action, ignore condition checks + if (FakeAccurateBlockPlacement.isHandling()){ + MessageHolder.sendDebugMessage(mc.player, "Passed because already handling something"); + return ActionResult.PASS; + } + RayTraceWrapper traceWrapper = RayTraceUtils.getGenericTrace(mc.world, mc.player, 6); + FakeAccurateBlockPlacement.requestedTicks = Math.max(-2, FakeAccurateBlockPlacement.requestedTicks); + if (traceWrapper == null) { + return ActionResult.PASS; + } + BlockHitResult trace = traceWrapper.getBlockHitResult(); + if (trace == null) { + return ActionResult.PASS; + } + World world = SchematicWorldHandler.getSchematicWorld(); + World clientWorld = mc.world; + BlockPos blockPos = trace.getBlockPos(); + if (isPositionCached(blockPos, false)){ + MessageHolder.sendDebugMessage(mc.player, "Passed because position "+ blockPos.toShortString() + " is cached"); + return ActionResult.PASS; + } + BlockState schematicState = world.getBlockState(blockPos); + BlockState clientState = clientWorld.getBlockState(blockPos); + if (schematicState.getBlock().getName().equals(clientState.getBlock().getName()) || schematicState.isAir()) { + MessageHolder.sendDebugMessage(mc.player, "Passed because position "+ blockPos.toShortString() + " is satisfied"); + return ActionResult.FAIL; + } + if (FakeAccurateBlockPlacement.canHandleOther(schematicState.getBlock().asItem()) && canPickBlock(mc, schematicState, blockPos)) { + MessageHolder.sendOrderMessage("Requested " + schematicState + " at " +blockPos.toShortString()); + FakeAccurateBlockPlacement.request(schematicState, blockPos); + return ActionResult.SUCCESS; + } + MessageHolder.sendDebugMessage(mc.player, "Passed because position "+ blockPos.toShortString() + " cannot pick block or cannot handle other, handling "+ FakeAccurateBlockPlacement.currentHandling); + return ActionResult.FAIL; + } + public static ActionResult doEasyPlaceNormally(MinecraftClient mc) { //force normal easyplace action, ignore condition checks + RayTraceWrapper traceWrapper = RayTraceUtils.getGenericTrace(mc.world, mc.player, 6); + if (traceWrapper == null) { + return ActionResult.PASS; + } + BlockHitResult trace = traceWrapper.getBlockHitResult(); + if (trace == null) { + return ActionResult.PASS; + } + World world = SchematicWorldHandler.getSchematicWorld(); + World clientWorld = mc.world; + BlockPos blockPos = trace.getBlockPos(); + BlockState schematicState = world.getBlockState(blockPos); + BlockState clientState = clientWorld.getBlockState(blockPos); + if (schematicState.getBlock().getName().equals(clientState.getBlock().getName())) { + return ActionResult.FAIL; + } + ItemStack stack = MaterialCache.getInstance().getRequiredBuildItemForState(schematicState); + if (!stack.isEmpty()) { + InventoryUtils.schematicWorldPickBlock(stack, blockPos, world, mc); + Hand hand = EntityUtils.getUsedHandForItem(mc.player, stack); + if (hand == null) { + return ActionResult.FAIL; + } + Vec3d hitPos; + Direction sideOrig = trace.getSide(); + Direction side = applyPlacementFacing(schematicState, sideOrig, clientState); + if (LitematicaMixinMod.PRINTER_ACCURATE_BLOCK_PLACEMENT.getBooleanValue()) { + hitPos = applyCarpetProtocolHitVec(blockPos, schematicState); + } else { + hitPos = applyHitVec(blockPos, schematicState, side); + } + BlockHitResult hitResult = new BlockHitResult(hitPos, side, blockPos, false); + boolean canContinue; + if (!LitematicaMixinMod.PRINTER_FAKE_ROTATION.getBooleanValue() || LitematicaMixinMod.PRINTER_ACCURATE_BLOCK_PLACEMENT.getBooleanValue()) { //Accurateblockplacement, or vanilla but no fake + canContinue = BedrockBreaker.interactBlock(mc, hitResult).isAccepted(); //PLACE block + cacheEasyPlacePosition(blockPos, false); + } else { + canContinue = FakeAccurateBlockPlacement.request(schematicState, blockPos); + } + if (canContinue) { + return ActionResult.SUCCESS; + } else { + return ActionResult.FAIL; + } + } + return ActionResult.FAIL; + } + + private static void recordCause(BlockPos pos, String reason, BlockPos reasonPos) { + if (reasonPos != null) { + if (pos.asLong() == reasonPos.asLong()) { + causeMap.put(pos.asLong(), "self registered+\n"); + //throw new AssertionError("Position should not equal to reason position!"); + } + referenceMap.put(pos.asLong(), reasonPos.asLong()); + } + causeMap.put(pos.asLong(), reason + '\n'); + } + + private static boolean containsPositionAsReason(BlockPos resultPos, BlockPos pos) { + // recursively check if the position is a reason for the result position + return containsPositionsAsReasonInternal(resultPos, pos, null); + } + + private static boolean containsPositionsAsReasonInternal(BlockPos resultPos, BlockPos pos, Set recursiveSet) { + if (recursiveSet == null) { + recursiveSet = new HashSet<>(); + } + if (recursiveSet.contains(resultPos.asLong())) { + return false; + } + recursiveSet.add(resultPos.asLong()); + if (resultPos.asLong() == pos.asLong()) { + return true; + } + if (referenceMap.containsKey(resultPos.asLong())) { + return containsPositionsAsReasonInternal(BlockPos.fromLong(referenceMap.get(resultPos.asLong())), pos, recursiveSet); + } + return false; + } + + + + private static String getReason(Long pos) { + return "<" + internalGetReason(pos, null, 0) + ">"; + } + + private static String internalGetReason(Long pos, LongOpenHashSet set, int count) { + if (count > 10) { + return BlockPos.fromLong(pos).toShortString() + "RECURSIVE_COUNT_EXCEED"; + } + if (set == null) { + set = new LongOpenHashSet(); + } + if (set.contains((long) pos)) { + return BlockPos.fromLong(pos).toShortString() + "Recursive "; + } + if (referenceMap.containsKey((long) pos)) { + set.add((long) pos); + return causeMap.getOrDefault(pos, BlockPos.fromLong(pos).toShortString() + " : Not registered") + " " + internalGetReason(referenceMap.get((long) pos), set, count + 1); + } + return causeMap.getOrDefault(pos, BlockPos.fromLong(pos).toShortString() + " : Not registered"); + } + + private static boolean isPositionWithinBox(Box box, BlockPos pos) { + if (box == null) { + return true; + } + BlockPos start = box.getPos1(); + BlockPos end = box.getPos2(); + BlockPos ref1 = new BlockPos(Math.min(start.getX(), end.getX()), Math.min(start.getY(), end.getY()), Math.min(start.getZ(), end.getZ())); + BlockPos ref2 = new BlockPos(Math.max(start.getX(), end.getX()), Math.max(start.getY(), end.getY()), Math.max(start.getZ(), end.getZ())); + return (ref1.getX() <= pos.getX() && pos.getX() <= ref2.getX() && ref1.getY() <= pos.getY() && pos.getY() <= ref2.getY() && ref1.getZ() <= pos.getZ() && pos.getZ() <= ref2.getZ()); + } + + private static boolean isPositionWithinBox(BlockPos pos) { + if (Printer.CURRENT_BOX == null) { + return true; + } + return isPositionWithinBox(Printer.CURRENT_BOX, pos); + } + + @Environment(EnvType.CLIENT) + synchronized public static ActionResult doPrinterAction(MinecraftClient mc) { + io.github.eatmyvenom.litematicin.utils.InventoryUtils.itemChangeCount = 0; + if (!LitematicaMixinMod.DEBUG_MESSAGE.getBooleanValue()) { + causeMap.clear(); //reduce ram usage + } + if (!LitematicaMixinMod.PRINTER_BEDROCK_BREAKING.getBooleanValue()) { + BedrockBreaker.clear(); + } + FakeAccurateBlockPlacement.requestedTicks = Math.max(-2, FakeAccurateBlockPlacement.requestedTicks); + if (breaker.isBreakingBlock()) { + mc.player.sendMessage(Text.of("Handling breakBlock!"), true); + return ActionResult.SUCCESS; + } + if (LitematicaMixinMod.PRINTER_ALLOW_INVENTORY_OPERATIONS.getBooleanValue()) { + ItemInputs.execute(mc); + mc.player.sendMessage(Text.of("Handling inventory operation!"), true); + return ActionResult.PASS; + } else { + ItemInputs.clear(); + } + if (new Date().getTime() < lastPlaced + 1000.0 * LitematicaMixinMod.EASY_PLACE_MODE_DELAY.getDoubleValue()) { + mc.player.sendMessage(Text.of("Handling delay"), true); + return ActionResult.PASS; + } else { + isSleeping = false; + } + boolean isCreative = isCreative(mc.player); + BlockPos tracePos = mc.player.getBlockPos(); + int posX = tracePos.getX(); + int posY = tracePos.getY(); + int posZ = tracePos.getZ(); + + RayTraceWrapper traceWrapper = RayTraceUtils.getGenericTrace(mc.world, mc.player, 6); + //RayTraceWrapper traceWrapper = RayTraceUtils.getGenericTrace(mc.world, mc.player, 6, true); previous litematica code + if (traceWrapper != null) { + BlockHitResult trace = traceWrapper.getBlockHitResult(); + tracePos = trace.getBlockPos(); + posX = tracePos.getX(); + posY = tracePos.getY(); + posZ = tracePos.getZ(); + } + + boolean ClearArea = LitematicaMixinMod.PRINTER_CLEAR_FLUIDS.getBooleanValue(); // if it's true, will ignore everything and remove fluids. + boolean UseCobble = LitematicaMixinMod.PRINTER_CLEAR_FLUIDS_USE_COBBLESTONE.getBooleanValue() && ClearArea; + boolean ClearSnow = LitematicaMixinMod.PRINTER_CLEAR_SNOW_LAYER.getBooleanValue() && ClearArea; + boolean CanUseProtocol = LitematicaMixinMod.PRINTER_ACCURATE_BLOCK_PLACEMENT.getBooleanValue(); + boolean FillInventory = LitematicaMixinMod.PRINTER_PUMPKIN_PIE_FOR_COMPOSTER.getBooleanValue(); + ItemStack composableItem = Items.PUMPKIN_PIE.getDefaultStack(); + //#if MC>=11902 + List allPlacementsTouchingSubChunk = DataManager.getSchematicPlacementManager().getAllPlacementsTouchingChunk(tracePos); + //#else + //$$ List allPlacementsTouchingSubChunk = DataManager.getSchematicPlacementManager().getAllPlacementsTouchingSubChunk(new SubChunkPos(tracePos)); + //#endif + Box selectedBox = null; + Printer.CURRENT_BOX = null; + if (allPlacementsTouchingSubChunk.isEmpty() && !ClearArea) { + if (LitematicaMixinMod.PRINTER_BEDROCK_BREAKING.getBooleanValue()) { + BedrockBreaker.scheduledTickHandler(mc, null); + } + return ActionResult.PASS; + } + int maxX = 0; + int maxY = 0; + int maxZ = 0; + int minX = 0; + int minY = 0; + int minZ = 0; + int rangeX = LitematicaMixinMod.EASY_PLACE_MODE_RANGE_X.getIntegerValue(); + int rangeY = LitematicaMixinMod.EASY_PLACE_MODE_RANGE_Y.getIntegerValue(); + int rangeZ = LitematicaMixinMod.EASY_PLACE_MODE_RANGE_Z.getIntegerValue(); + if (rangeX == 0 && rangeY == 0 && rangeZ == 0 && traceWrapper != null) { + return doEasyPlaceNormally(mc); + } + boolean foundBox = false; + if (ClearArea) { + foundBox = true; + maxX = posX + rangeX; + maxY = posY + rangeY; + maxZ = posZ + rangeZ; + minX = posX - rangeX; + minY = posY - rangeY; + minZ = posZ - rangeZ; + } else { + for (PlacementPart part : allPlacementsTouchingSubChunk) { + IntBoundingBox pbox = part.getBox(); + if (pbox.containsPos(tracePos)) { + + ImmutableMap boxes = part.getPlacement() + .getSubRegionBoxes(RequiredEnabled.PLACEMENT_ENABLED); + + for (Box box : boxes.values()) { + + final int boxXMin = Math.min(box.getPos1().getX(), box.getPos2().getX()); + final int boxYMin = Math.min(box.getPos1().getY(), box.getPos2().getY()); + final int boxZMin = Math.min(box.getPos1().getZ(), box.getPos2().getZ()); + final int boxXMax = Math.max(box.getPos1().getX(), box.getPos2().getX()); + final int boxYMax = Math.max(box.getPos1().getY(), box.getPos2().getY()); + final int boxZMax = Math.max(box.getPos1().getZ(), box.getPos2().getZ()); + if (posX < boxXMin || posX > boxXMax || posY < boxYMin || posY > boxYMax || posZ < boxZMin + || posZ > boxZMax) { + continue; + } + minX = boxXMin; + maxX = boxXMax; + minY = boxYMin; + maxY = boxYMax; + minZ = boxZMin; + maxZ = boxZMax; + foundBox = true; + selectedBox = box; + CURRENT_BOX = box; + break; + } + + break; + } + } + } + + if (!foundBox) { + if (LitematicaMixinMod.PRINTER_BEDROCK_BREAKING.getBooleanValue()) { + BedrockBreaker.scheduledTickHandler(mc, null); + } + return ActionResult.PASS; + } + LayerRange range = DataManager.getRenderLayerRange(); //add range following + int MaxReach = Math.max(Math.max(rangeX, rangeY), rangeZ); + boolean breakBlocks = LitematicaMixinMod.PRINTER_BREAK_BLOCKS.getBooleanValue(); + boolean ExplicitObserver = LitematicaMixinMod.PRINTER_OBSERVER_AVOID_ALL.getBooleanValue(); + boolean flipBlocks = mc.player.getMainHandStack().getItem().equals(Items.CACTUS) && LitematicaMixinMod.PRINTER_FLIPPINCACTUS.getBooleanValue(); //if true, will flip blocks with cactus + boolean smartRedstone = LitematicaMixinMod.PRINTER_SMART_REDSTONE_AVOID.getBooleanValue(); + Direction[] facingSides = Direction.getEntityFacingOrder(mc.player); + Direction primaryFacing = facingSides[0]; + Direction horizontalFacing = primaryFacing; // For use in blocks with only horizontal rotation + + int index = 0; + while (horizontalFacing.getAxis() == Direction.Axis.Y && index < facingSides.length) { + horizontalFacing = facingSides[index++]; + } + + World world = SchematicWorldHandler.getSchematicWorld(); + + /* + * TODO: THIS IS REALLY BAD IN TERMS OF EFFICIENCY. I suggest using some form of + * search with a built in datastructure first Maybe quadtree? (I dont know how + * MC works) + */ + + int maxInteract = LitematicaMixinMod.PRINTER_MAX_BLOCKS.getIntegerValue(); + int interact = 0; + + int fromX = Math.max(posX - rangeX, minX); + int fromY = Math.max(posY - rangeY, minY); + int fromZ = Math.max(posZ - rangeZ, minZ); + + int toX = Math.min(posX + rangeX, maxX); + int toY = Math.min(posY + rangeY, maxY); + int toZ = Math.min(posZ + rangeZ, maxZ); + + toY = Math.max(Math.min(toY, worldTopY), worldBottomY); + fromY = Math.max(Math.min(fromY, worldTopY), worldBottomY); + + fromX = Math.max(fromX, (int) mc.player.getX() - rangeX); + fromY = Math.max(fromY, (int) mc.player.getY() - rangeY); + fromZ = Math.max(fromZ, (int) mc.player.getZ() - rangeZ); + + toX = Math.min(toX, (int) mc.player.getX() + rangeX); + toY = Math.min(toY, (int) mc.player.getY() + rangeY); + toZ = Math.min(toZ, (int) mc.player.getZ() + rangeZ); + for (int y = fromY; y <= toY; y++) { + for (int x = fromX; x <= toX; x++) { + for (int z = fromZ; z <= toZ; z++) { + if (interact >= maxInteract) { + if (shouldSleepLonger) { + shouldSleepLonger = false; + lastPlaced = Math.max(lastPlaced, new Date().getTime() + LitematicaMixinMod.PRINTER_SLEEP_STACK_EMPTIED.getIntegerValue()); + } else { + lastPlaced = Math.max(lastPlaced, new Date().getTime()); + } + return ActionResult.SUCCESS; + } + if (FakeAccurateBlockPlacement.emptyWaitingQueue()) { + interact++; + } + if (FakeAccurateBlockPlacement.shouldReturnValue) { + FakeAccurateBlockPlacement.shouldReturnValue = false; + return ActionResult.SUCCESS; + } + double dx = mc.player.getX() - x - 0.5; + double dy = mc.player.getY() - y - 0.5; + double dz = mc.player.getZ() - z - 0.5; + + if (dx * dx + dy * dy + dz * dz > MaxReach * MaxReach) { + continue; + } + + BlockPos pos = new BlockPos(x, y, z); + if (LitematicaMixinMod.PRINTER_ALLOW_INVENTORY_OPERATIONS.getBooleanValue() && io.github.eatmyvenom.litematicin.utils.InventoryUtils.hasItemInSchematic(world, pos)) { + MessageHolder.sendUniqueMessageAlways("Inventory in " + pos.toShortString() + " has Item inside!"); + } + BlockState stateSchematic; + BlockState stateClient; + updateSignText(mc, world, pos); + if (!breakBlocks && !ClearArea && !flipBlocks && !LitematicaMixinMod.PRINTER_BEDROCK_BREAKING.getBooleanValue()) { + if (world.isAir(pos)) { + continue; + } else { + if (world.getBlockState(pos) == mc.world.getBlockState(pos)) { + continue; + } + } + } + if (breakBlocks) { + if (LitematicaMixinMod.PRINTER_BREAK_IGNORE_EXTRA.getBooleanValue() && world.isAir(pos)) { + continue; + } + } + stateSchematic = world.getBlockState(pos); + stateClient = mc.world.getBlockState(pos); + if (!ClearArea) { + if (!range.isPositionWithinRange(pos)) { + continue; + } + if (breakBlocks && stateSchematic != null && !(stateClient.getBlock() instanceof SnowBlock) && + !stateClient.isAir() && + !(stateClient.isOf(Blocks.WATER) || stateClient.isOf(Blocks.LAVA) || stateClient.isOf(Blocks.BUBBLE_COLUMN)) && + !stateClient.isOf(Blocks.PISTON_HEAD) && !stateClient.isOf(Blocks.MOVING_PISTON)) { + if (!stateClient.getBlock().getName().equals(stateSchematic.getBlock().getName()) || + (stateClient.getBlock() instanceof SlabBlock && stateSchematic.getBlock() instanceof SlabBlock && stateClient.get(SlabBlock.TYPE) != stateSchematic.get(SlabBlock.TYPE)) + && dx * dx + Math.pow(dy + 1.5, 2) + dz * dz <= MaxReach * MaxReach) { + + if (isCreative(mc.player)) { + mc.interactionManager.attackBlock(pos, Direction.DOWN); + interact++; + + if (interact >= maxInteract) { + if (shouldSleepLonger) { + shouldSleepLonger = false; + lastPlaced = Math.max(lastPlaced, new Date().getTime() + LitematicaMixinMod.PRINTER_SLEEP_STACK_EMPTIED.getIntegerValue()); + } else { + lastPlaced = Math.max(lastPlaced, new Date().getTime()); + } + return ActionResult.SUCCESS; + } + } else if (BedrockBreaker.isBlockNotInstantBreakable(stateClient.getBlock()) && LitematicaMixinMod.PRINTER_BEDROCK_BREAKING.getBooleanValue()) { + mc.player.sendMessage(Text.of("Handling printerBedrockBreaking!"), true); + interact += BedrockBreaker.scheduledTickHandler(mc, pos); + continue; + } else if (LitematicaMixinMod.PRINTER_BEDROCK_BREAKING.getBooleanValue()) { + mc.player.sendMessage(Text.of("Handling printerBedrockBreaking!"), true); + interact += BedrockBreaker.scheduledTickHandler(mc, null); + continue; + } else if (!positionStorage.hasPos(pos)) { // For survival + boolean replaceable = BedrockBreaker.isReplaceable(mc.world.getBlockState(pos)); + if (!replaceable && mc.world.getBlockState(pos).getHardness(world, pos) == -1) { + continue; + } + if (replaceable || mc.world.getBlockState(pos).getHardness(world, pos) == 0) { + mc.interactionManager.attackBlock(pos, Direction.DOWN); + return ActionResult.SUCCESS; + } + if (!replaceable) { + if (breaker.startBreakingBlock(pos, mc)) { + return ActionResult.SUCCESS; + } + } // it needs to avoid unbreakable blocks and just added and lava, but it's not block so somehow made it work + continue; + } + } + } + // Abort if there is already a block in the target position + if (flipBlocks || requiresMoreAction(stateSchematic, stateClient)) { + /* + * Sometimes, blocks have other states like the delay on a repeater. So, this + * code clicks the block until the state is the same I don't know if Schematica + * does this too, I just did it because I work with a lot of redstone + */ + if (!flipBlocks && !stateClient.isAir() && !mc.player.isSneaking() && !isPositionCached(pos, true)) { + Block cBlock = stateClient.getBlock(); + Block sBlock = stateSchematic.getBlock(); + // Blocks are equal, but have different states + if (cBlock.getName().equals(sBlock.getName())) { + Direction facingSchematic = fi.dy.masa.malilib.util.BlockUtils + .getFirstPropertyFacingValue(stateSchematic); + Direction facingClient = fi.dy.masa.malilib.util.BlockUtils + .getFirstPropertyFacingValue(stateClient); + + if (facingSchematic == facingClient) { + int clickTimes = 0; + Direction side = Direction.NORTH; + if (sBlock instanceof RepeaterBlock && !LitematicaMixinMod.PRINTER_ACCURATE_BLOCK_PLACEMENT.getBooleanValue()) { + int clientDelay = stateClient.get(RepeaterBlock.DELAY); + int schematicDelay = stateSchematic.get(RepeaterBlock.DELAY); + if (clientDelay != schematicDelay) { + + if (clientDelay < schematicDelay) { + clickTimes = schematicDelay - clientDelay; + } else if (clientDelay > schematicDelay) { + clickTimes = schematicDelay + (4 - clientDelay); + } + } + side = Direction.UP; + } else if (sBlock instanceof ComparatorBlock && !LitematicaMixinMod.PRINTER_ACCURATE_BLOCK_PLACEMENT.getBooleanValue()) { + if (stateSchematic.get(ComparatorBlock.MODE) != stateClient + .get(ComparatorBlock.MODE)) { + clickTimes = 1; + } + side = Direction.UP; + } else if (sBlock instanceof LeverBlock) { + if (stateSchematic.get(LeverBlock.POWERED) != stateClient + .get(LeverBlock.POWERED)) { + clickTimes = 1; + } + + /* + * I don't know if this direction code is needed. I am just doing it anyway to + * make it "make sense" to the server (I am emulating what the client does so + * the server isn't confused) + */ + if (stateClient.get(LeverBlock.FACE) == BlockFace.CEILING) { + side = Direction.DOWN; + } else if (stateClient.get(LeverBlock.FACE) == BlockFace.FLOOR) { + side = Direction.UP; + } else { + side = stateClient.get(LeverBlock.FACING); + } + + } else if (sBlock instanceof TrapdoorBlock) { + if (!stateSchematic.isOf(Blocks.IRON_TRAPDOOR) && stateSchematic + .get(TrapdoorBlock.OPEN) != stateClient.get(TrapdoorBlock.OPEN)) { + clickTimes = 1; + } + } else if (sBlock instanceof FenceGateBlock) { + if (stateSchematic.get(FenceGateBlock.OPEN) != stateClient + .get(FenceGateBlock.OPEN)) { + clickTimes = 1; + } + } else if (sBlock instanceof DoorBlock) { + if (!stateSchematic.isOf(Blocks.IRON_DOOR) && stateSchematic + .get(DoorBlock.OPEN) != stateClient.get(DoorBlock.OPEN)) { + clickTimes = 1; + } + } else if (sBlock instanceof NoteBlock) { + int note = stateClient.get(NoteBlock.NOTE); + int targetNote = stateSchematic.get(NoteBlock.NOTE); + if (note != targetNote) { + if (note < targetNote) { + clickTimes = targetNote - note; + } else if (note > targetNote) { + clickTimes = targetNote + (25 - note); + } + } + } else if (sBlock instanceof ComposterBlock && FillInventory) { + if (!FakeAccurateBlockPlacement.canHandleOther(composableItem.getItem())) { + continue; + } + int level = stateClient.get(ComposterBlock.LEVEL); + int Schematiclevel = stateSchematic.get(ComposterBlock.LEVEL); + if (level != Schematiclevel && !(level == 7 && Schematiclevel == 8)) { + if (doSchematicWorldPickBlock(mc, composableItem)) { + Vec3d hitPos = new Vec3d(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5); + BlockHitResult hitResult = new BlockHitResult(hitPos, side, pos, false); + BedrockBreaker.interactBlock(mc, hitResult); //COMPOSTER + io.github.eatmyvenom.litematicin.utils.InventoryUtils.decrementCount(isCreative); + cacheEasyPlacePosition(pos, false); + if (shouldSleepLonger) { + shouldSleepLonger = false; + lastPlaced = Math.max(lastPlaced, new Date().getTime() + 200 + LitematicaMixinMod.PRINTER_SLEEP_STACK_EMPTIED.getIntegerValue()); + } else { + lastPlaced = Math.max(lastPlaced, new Date().getTime() + 200); + } + return ActionResult.SUCCESS; + } + } else { + cacheEasyPlacePosition(pos, true); + } + } else if (!isPositionCached(pos, false) && LitematicaMixinMod.PRINTER_PLACE_MINECART.getBooleanValue() && sBlock instanceof DetectorRailBlock && cBlock instanceof DetectorRailBlock) { + if (!shouldAvoidPlaceCart(pos, world) && placeCart(stateSchematic, mc, pos)) { + continue; + } + } + for (int i = 0; i < clickTimes; i++) // Click on the block a few times + { + Vec3d hitPos = Vec3d.ofCenter(pos); + + BlockHitResult hitResult = new BlockHitResult(hitPos, side, pos, false); + + BedrockBreaker.interactBlock(mc, hitResult); //NOTEBLOCK, REPEATER... + interact++; + } + + if (clickTimes > 0) { + cacheEasyPlacePosition(pos, true, 3600); + } + + } //can place vanilla + } + // Blocks are not equal, but can be converted. example: dirt -> dirt path + if (stateClient.isOf(Blocks.DIRT)) { + if (stateSchematic.isOf(Blocks.DIRT_PATH) && LitematicaMixinMod.PRINTER_PRINT_DIRT_VARIANTS.getBooleanValue() && io.github.eatmyvenom.litematicin.utils.InventoryUtils.canSwap(mc.player, item -> item.getItem() instanceof ShovelItem)) { + if (doSchematicWorldPickBlock(mc, stack -> stack.getItem() instanceof ShovelItem)){ + Vec3d hitPos = new Vec3d(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5); + BlockHitResult hitResult = new BlockHitResult(hitPos, Direction.UP, pos, false); + BedrockBreaker.interactBlock(mc, hitResult); + } + } + // farmland + else if (stateSchematic.isOf(Blocks.FARMLAND) && LitematicaMixinMod.PRINTER_PRINT_DIRT_VARIANTS.getBooleanValue() && io.github.eatmyvenom.litematicin.utils.InventoryUtils.canSwap(mc.player, item -> item.getItem() instanceof HoeItem)) { + if (doSchematicWorldPickBlock(mc, stack -> stack.getItem() instanceof HoeItem)){ + Vec3d hitPos = new Vec3d(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5); + BlockHitResult hitResult = new BlockHitResult(hitPos, Direction.UP, pos, false); + BedrockBreaker.interactBlock(mc, hitResult); + } + } + } + } else if (!ClearArea && flipBlocks) { + // Flip the block + Block cBlock = stateClient.getBlock(); + Block sBlock = stateSchematic.getBlock(); + if (cBlock.getName().equals(sBlock.getName())) { + boolean ShapeBoolean = false; + boolean ShouldFix = false; + if (sBlock instanceof AbstractRailBlock) { + if (sBlock instanceof RailBlock) { + String SchematicRailShape = stateSchematic.get(RailBlock.SHAPE).toString(); + String ClientRailShape = stateClient.get(RailBlock.SHAPE).toString(); + ShouldFix = !Objects.equals(SchematicRailShape, ClientRailShape); + ShapeBoolean = !Objects.equals(SchematicRailShape, ClientRailShape) && ((Objects.equals(SchematicRailShape, "south_west") || Objects.equals(SchematicRailShape, "north_west") || + Objects.equals(SchematicRailShape, "south_east") || Objects.equals(SchematicRailShape, "north_east")) && (Objects.equals(ClientRailShape, "south_west") || + Objects.equals(ClientRailShape, "north_west") || Objects.equals(ClientRailShape, "south_east") || Objects.equals(ClientRailShape, "north_east")) || + (Objects.equals(SchematicRailShape, "east_west") || Objects.equals(SchematicRailShape, "north_south")) && (Objects.equals(ClientRailShape, "east_west") || Objects.equals(ClientRailShape, "north_south"))); + } else { + String SchematicRailShape = stateSchematic.get(PoweredRailBlock.SHAPE).toString(); + String ClientRailShape = stateClient.get(PoweredRailBlock.SHAPE).toString(); + ShouldFix = !Objects.equals(SchematicRailShape, ClientRailShape); + ShapeBoolean = !Objects.equals(SchematicRailShape, ClientRailShape) && (Objects.equals(SchematicRailShape, "east_west") || Objects.equals(SchematicRailShape, "north_south")) && + (Objects.equals(ClientRailShape, "east_west") || Objects.equals(ClientRailShape, "north_south")); + } + } else if (sBlock instanceof ObserverBlock || sBlock instanceof PistonBlock || sBlock instanceof RepeaterBlock || sBlock instanceof ComparatorBlock || sBlock instanceof FenceGateBlock || sBlock instanceof TrapdoorBlock) { + Direction facingSchematic = fi.dy.masa.malilib.util.BlockUtils.getFirstPropertyFacingValue(stateSchematic); + Direction facingClient = fi.dy.masa.malilib.util.BlockUtils.getFirstPropertyFacingValue(stateClient); + ShouldFix = facingSchematic != facingClient; + ShapeBoolean = facingClient.getOpposite().equals(facingSchematic); + } + Direction side = Direction.UP; + if (ShapeBoolean) { + Vec3d hitPos = Vec3d.ofCenter(pos); + BlockHitResult hitResult = new BlockHitResult(hitPos, side, pos, false); + BedrockBreaker.interactBlock(mc, hitResult); //CACTUS + cacheEasyPlacePosition(pos, true); + interact++; + } else if (breakBlocks && ShouldFix) { //cannot fix via flippincactus + mc.interactionManager.attackBlock(pos, Direction.DOWN);//by one hit possible? + breaker.startBreakingBlock(pos, mc); //register + return ActionResult.SUCCESS; + } + continue; + } + } //flip + continue; + } //cancel normal placing + } + if (!ClearArea && flipBlocks) { + mc.player.sendMessage(Text.of("Handling printerFlippinCactus!"), true); + continue; + } + if (isPositionCached(pos, false) || LitematicaMixinMod.PRINTER_BEDROCK_BREAKING.getBooleanValue() || (!(stateSchematic.getBlock() instanceof NetherPortalBlock) && stateSchematic.isAir() && !ClearArea)) { + continue; + } + ItemStack stack = MaterialCache.getInstance().getRequiredBuildItemForState(stateSchematic); + Block cBlock = stateClient.getBlock(); + Block sBlock = stateSchematic.getBlock(); + if (ClearArea) { + MessageHolder.sendUniqueMessageActionBar(mc.player, "Handling printerClearArea!"); + if (isReplaceableWaterFluidSource(stateClient)) { + if (!UseCobble) { + stack = Items.SPONGE.getDefaultStack(); + } else { + stack = Items.COBBLESTONE.getDefaultStack(); + } + } else if (stateClient.getFluidState().getFluid() instanceof LavaFluid && stateClient.contains(FluidBlock.LEVEL) && stateClient.get(FluidBlock.LEVEL) == 0) { + if (!UseCobble) { + stack = Items.SLIME_BLOCK.getDefaultStack(); + } else { + stack = Items.COBBLESTONE.getDefaultStack(); + } + } else if (ClearSnow && cBlock instanceof SnowBlock) { + stack = Items.STRING.getDefaultStack(); + } else { + continue; + } + } + if (ClearArea) { + if (ClearArea && doSchematicWorldPickBlock(mc, stack)) { + Vec3d hitPos = Vec3d.ofCenter(pos).add(0, 0.5, 0); + BlockHitResult hitResult = new BlockHitResult(hitPos, Direction.UP, pos, false); + BedrockBreaker.interactBlock(mc, hitResult); //FLUID REMOVAL + io.github.eatmyvenom.litematicin.utils.InventoryUtils.decrementCount(isCreative); + interact++; + cacheEasyPlacePosition(pos, false); + sleepWhenRequired(mc); + if (isReplaceableFluidSource(stateClient) || cBlock instanceof SnowBlock) { + lastPlaced = new Date().getTime() + 200; + } + } + continue; + } + if (!FakeAccurateBlockPlacement.canPlace(stateSchematic, pos)) { + continue; + } + if (sBlock instanceof PistonHeadBlock || stateSchematic.isOf(Blocks.MOVING_PISTON)) { + continue; + } + if (stateSchematic == stateClient) { + // Right block is in place, no need to place it again + causeMap.remove(pos.asLong()); + continue; + } + if (cBlock != sBlock && !BedrockBreaker.isReplaceable(stateClient)) { + // Wrong block is in place, requires player action to fix + MessageHolder.sendUniqueMessage(mc.player, sBlock.getTranslationKey() + " at " + pos.toShortString() + " is blocking placement of " + cBlock.getTranslationKey() + "!!"); + continue; + } + if (canPickBlock(mc, stateSchematic, pos)) { + // We can pick the block. + if (willFall(stateSchematic, mc.world, pos)) { + // Block will fall, don't place it + recordCause(pos, stateSchematic.getBlock().getTranslationKey() + " at " + pos.toShortString() + " is Falling block", pos.down()); + MessageHolder.sendUniqueMessage(mc.player, getReason(pos.asLong())); + continue; + } else if (!LitematicaMixinMod.PRINTER_PLACE_ICE.getBooleanValue() && stateSchematic.isOf(Blocks.WATER)) { + // Block is water, don't place it + recordCause(pos, stateSchematic.getBlock().getTranslationKey() + " at " + pos.toShortString() + " is water", pos); + MessageHolder.sendUniqueMessage(mc.player, getReason(pos.asLong())); + continue; + } else if (!LitematicaMixinMod.PRINTER_PLACE_ICE.getBooleanValue() && stateSchematic.isOf(Blocks.LAVA)) { + // Block is lava, don't place it + recordCause(pos, stateSchematic.getBlock().getTranslationKey() + " at " + pos.toShortString() + " is lava", pos); + MessageHolder.sendUniqueMessage(mc.player, getReason(pos.asLong())); + continue; + } else if (sBlock instanceof FallingBlock) { + // Falling blocks, check if they have support + BlockPos Offsetpos = new BlockPos(x, y - 1, z); + BlockState OffsetstateSchematic = world.getBlockState(Offsetpos); + BlockState OffsetstateClient = mc.world.getBlockState(Offsetpos); + if (OffsetstateClient.isAir() || (breakBlocks && !OffsetstateClient.getBlock().getName().equals(OffsetstateSchematic.getBlock().getName()))) { + recordCause(pos, stateSchematic.getBlock().getTranslationKey() + " at " + pos.toShortString() + " is Falling block", pos.down()); + MessageHolder.sendUniqueMessage(mc.player, getReason(pos.asLong())); + continue; + } + } + boolean markObserverToBePlaced = false; // If this flag is true, we will place observer ignoring checks + // BUD, for positions near piston with BUD, place block first. + if (smartRedstone) { + if (sBlock instanceof RedstoneBlock) { + if (isQCable(mc, world, pos)) { + recordCause(pos, sBlock.getTranslationKey() + " at " + pos.toShortString() + "will QC, waiting other block at ", isQCablePos(mc, world, pos)); + MessageHolder.sendUniqueMessage(mc.player, getReason(pos.asLong())); + continue; + } + } else if (sBlock instanceof TntBlock) { + if (mc.world.isReceivingRedstonePower(pos)) { + recordCause(pos, sBlock.getTranslationKey() + " at " + pos.toShortString() + " is now receiving power!", pos); + MessageHolder.sendUniqueMessage(mc.player, getReason(pos.asLong())); + continue; + } + } else if (sBlock instanceof PistonBlock) { + if (!shouldExtendQC(mc, world, pos)) { + recordCause(pos, sBlock.getTranslationKey() + " at " + pos.toShortString() + " is QC", pos); + MessageHolder.sendUniqueMessage(mc.player, getReason(pos.asLong())); + continue; + } else if (hasNearbyRedirectDust(mc, world, pos)) { + recordCause(pos, sBlock.getTranslationKey() + " at " + pos.toShortString() + " has redirectable dust nearby at " + hasNearbyRedirectDustPos(mc, world, pos).toShortString(), hasNearbyRedirectDustPos(mc, world, pos)); + MessageHolder.sendUniqueMessage(mc.player, getReason(pos.asLong())); + continue; + } + if (cantAvoidExtend(mc.world, pos, world)) { + recordCause(pos, sBlock.getTranslationKey() + " at " + pos.toShortString() + " will unexpectedly extend", pos); + MessageHolder.sendUniqueMessage(mc.player, getReason(pos.asLong())); + continue; + } + if (shouldSuppressExtend(world, pos) && hasWrongStateNearby(mc, world, pos)) { + recordCause(pos, sBlock.getTranslationKey() + " at " + " is BUD but has wrong state nearby \n" + hasWrongStateNearbyReason(mc, world, pos), hasWrongStateNearbyPos(mc, world, pos)); + MessageHolder.sendUniqueMessage(mc.player, getReason(pos.asLong())); + continue; + } + if (willExtendInWorld(world, pos, stateSchematic.get(PistonBlock.FACING)) != stateSchematic.get(PistonBlock.EXTENDED) && directlyPowered(world, pos, stateSchematic.get(PistonBlock.FACING))) { + if (LitematicaMixinMod.PRINTER_SUPPRESS_PUSH_LIMIT.getBooleanValue()) { + recordCause(pos, sBlock.getTranslationKey() + " at " + pos.toShortString() + " should respect push limit because its directly powered", pos); + MessageHolder.sendUniqueMessage(mc.player, getReason(pos.asLong())); + continue; + } + MessageHolder.sendUniqueMessage(mc.player, sBlock.getTranslationKey() + " at " + " is placed ignoring push limit checks, check printerSuppressPushLimitPistons option."); + } + } else if (sBlock instanceof ObserverBlock) { + if (ObserverUpdateOrder(mc, world, pos, selectedBox)) { + if (LitematicaMixinMod.PRINTER_FLIPPINCACTUS.getBooleanValue() && canBypass(mc, world, pos)) { + stateSchematic = stateSchematic.with(ObserverBlock.FACING, stateSchematic.get(ObserverBlock.FACING).getOpposite()); + } else { + BlockPos causedPos = ObserverUpdateOrderPos(mc, world, pos); + if (causedPos.asLong() == pos.asLong()) { + MessageHolder.sendUniqueMessage(mc.player, "Observer at " + pos.toShortString() + " is causing self-blocking, check manually"); + } + //TODO : if causedPos is not placeable by observer, then it will be stuck in loop. + //Thus if observer's output is 'safe', then we will force place it. + if (containsPositionAsReason(causedPos, pos)) { + // We have to place observer first, then place the block. + MessageHolder.sendUniqueMessage(mc.player, "Observer at " + pos.toShortString() + " is causing self-blocking, checking if it can be placed"); + if (checkObserverOutputs(mc, world, pos)) { + MessageHolder.sendUniqueMessage(mc.player, "Observer at " + pos.toShortString() + " can be placed, placing it"); + // mark observer output positions as cached + markObserverOutputs(mc, world, pos); + markObserverToBePlaced = true; + } + else { + recordCause(pos, sBlock.getTranslationKey() + " at " + pos.toShortString() + " is waiting for ", causedPos); + MessageHolder.sendUniqueMessage(mc.player, getReason(pos.asLong())); + continue; + } + } + else { + recordCause(pos, sBlock.getTranslationKey() + " at " + pos.toShortString() + " is waiting for ", causedPos); + MessageHolder.sendUniqueMessage(mc.player, getReason(pos.asLong())); + continue; + } + } + } + } + } + if (smartRedstone && ExplicitObserver) { + BlockPos observerPos = isObserverCantAvoidOutput(mc, world, pos); + if (observerPos != null) { + recordCause(pos, sBlock.getTranslationKey() + " at " + pos.toShortString() + " is waiting for preceded observer at " + observerPos.toShortString(), observerPos); + MessageHolder.sendUniqueMessage(mc.player, getReason(pos.asLong())); + cacheEasyPlacePosition(pos, false, 400); // cache for 400 ms + continue; + } + if (sBlock instanceof ObserverBlock && !markObserverToBePlaced) { + Pair value = isWatchingCorrectState(mc, world, pos, null, true); + if (!value.getLeft()) { + recordCause(pos, sBlock.getTranslationKey() + " at " + pos.toShortString() + " can't be placed due to " + value.getRight().toShortString(), value.getRight()); + MessageHolder.sendUniqueMessage(mc.player, getReason(pos.asLong())); + continue; + } + } + } + if (sBlock instanceof NetherPortalBlock && !sBlock.getName().equals(cBlock.getName()) && + //#if MC>=11700 + NetherPortal.getNewPortal(mc.world, pos, Direction.Axis.X).isPresent() + //#else + //$$ AreaHelper.method_30485(mc.world, pos, Direction.Axis.X).isPresent() + //#endif + ) { + ItemStack lightStack = Items.FIRE_CHARGE.getDefaultStack(); + if (getSlotWithStack(mc.player, lightStack) == -1) { + lightStack = Items.FLINT_AND_STEEL.getDefaultStack(); + } + BlockPos offsetPos = new BlockPos(x, y - 1, z); + BlockState offsetStateSchematic = world.getBlockState(offsetPos); + BlockState offsetStateClient = mc.world.getBlockState(offsetPos); + if (getSlotWithStack(mc.player, lightStack) == -1 || offsetStateClient.isAir() || (!offsetStateClient.getBlock().getName().equals(offsetStateSchematic.getBlock().getName()))) { + continue; + } + if (doSchematicWorldPickBlock(mc, lightStack)) { + Vec3d hitPos = Vec3d.ofCenter(new BlockPos(x, y - 1, z)).add(0, 0.5, 0); + BlockHitResult hitResult = new BlockHitResult(hitPos, Direction.UP, new BlockPos(x, y - 1, z), false); + BedrockBreaker.interactBlock(mc, hitResult); //LIGHT + cacheEasyPlacePosition(pos, false); + sleepWhenRequired(mc); + if (shouldSleepLonger) { + shouldSleepLonger = false; + lastPlaced = Math.max(lastPlaced, new Date().getTime() + 200 + LitematicaMixinMod.PRINTER_SLEEP_STACK_EMPTIED.getIntegerValue()); + } else { + lastPlaced = Math.max(lastPlaced, new Date().getTime() + 200); + interact++; + } + return ActionResult.SUCCESS; + } + } + Direction facing = fi.dy.masa.malilib.util.BlockUtils.getFirstPropertyFacingValue(stateSchematic); + if (facing != null) { + facing = facing.getOpposite(); + } + if (stateSchematic.getBlock() instanceof AbstractRailBlock) { + facing = convertRailShapetoFace(stateSchematic); + } + if (facing != null) { + FacingData facedata = FacingData.getFacingData(stateSchematic); + if (facedata == null && !(stateSchematic.getBlock() instanceof AbstractRailBlock) && !simulateFacingData(stateSchematic, pos, Vec3d.ofCenter(pos)) ) { + MessageHolder.sendMessageUncheckedUnique(mc.player, stateSchematic.getBlock() + " does not have facing data, please add this!"); + if (LitematicaMixinMod.PRINTER_SKIP_UNKNOWN_BLOCKSTATE.getBooleanValue()) continue; + + } + if (!(CanUseProtocol && IsBlockSupportedCarpet(stateSchematic.getBlock())) && !LitematicaMixinMod.PRINTER_FAKE_ROTATION.getBooleanValue() && !canPlaceFace(facedata, stateSchematic, primaryFacing, horizontalFacing)) { + continue; + } + + if ((stateSchematic.getBlock() instanceof DoorBlock + && stateSchematic.get(DoorBlock.HALF) == DoubleBlockHalf.UPPER) + || (stateSchematic.getBlock() instanceof BedBlock + && stateSchematic.get(BedBlock.PART) == BedPart.HEAD)) { + continue; + } + } + + // Exception for signs (edge case) + if (stateSchematic.getBlock() instanceof SignBlock + && !(stateSchematic.getBlock() instanceof WallSignBlock)) { + if ((MathHelper.floor((double) ((180.0F + FakeAccurateBlockPlacement.getYaw(mc.player)) * 16.0F / 360.0F) + 0.5D) + & 15) != stateSchematic.get(SignBlock.ROTATION)) { + continue; + } + } + Direction sideOrig = Direction.NORTH; + BlockPos npos = pos; + Direction side = applyPlacementFacing(stateSchematic, sideOrig, stateClient); + Block blockSchematic = stateSchematic.getBlock(); + //Don't place waterlogged block's original block before fluid since its painful + // 1. if + if (LitematicaMixinMod.PRINTER_PLACE_ICE.getBooleanValue() && + (isReplaceableWaterFluidSource(stateSchematic) && BedrockBreaker.isReplaceable(stateClient) && !isReplaceableWaterFluidSource(stateClient) && !stateClient.isOf(Blocks.LAVA) || + LitematicaMixinMod.PRINTER_WATERLOGGED_WATER_FIRST.getBooleanValue() && BedrockBreaker.isReplaceable(stateClient) && containsWaterloggable(stateSchematic)) + ) { + ItemStack iceStack = Items.ICE.getDefaultStack(); + if (!FakeAccurateBlockPlacement.canHandleOther(iceStack.getItem())) { + continue; + } + if (doSchematicWorldPickBlock(mc, iceStack)) { + BedrockBreaker.interactBlock(mc, new BlockHitResult(new Vec3d(pos.getX(), pos.getY(), pos.getZ()), Direction.DOWN, pos, false)); + io.github.eatmyvenom.litematicin.utils.InventoryUtils.decrementCount(isCreative); + cacheEasyPlacePosition(pos, false); + sleepWhenRequired(mc); + interact++; + } //ICE + else { + recordCause(pos, "Can't pick item " + Items.ICE.getTranslationKey() + " at " + pos.toShortString(), pos); + } + continue; + } + if (!canPickBlock(mc, stateSchematic, pos)) { + //mc.player.sendMessage(Text.of("Can't pick block"),true); + recordCause(pos, "Can't pick item " + stateSchematic.getBlock().asItem().getTranslationKey() + " at " + pos.toShortString(), pos); + MessageHolder.sendUniqueMessage(mc.player, "Can't pick item " + stateSchematic.getBlock().asItem().getTranslationKey() + " at " + pos.toShortString()); + continue; + } + //if (!blockSchematic.canPlaceAt(stateSchematic, mc.world, pos)) { + if (!stateSchematic.canPlaceAt(mc.world, pos)) { + recordCause(pos, stateSchematic.getBlock().toString() + "(" + pos.toShortString() + ", can't be placed)", pos); + MessageHolder.sendUniqueMessage(mc.player, stateSchematic.getBlock().getTranslationKey() + " can't be placed at " + pos.toShortString()); + continue; + } + if (blockSchematic instanceof GrindstoneBlock) { + placeGrindStone(stateSchematic, mc, pos); + interact++; + continue; + } + if (blockSchematic instanceof TrapdoorBlock && !CanUseProtocol && !LitematicaMixinMod.PRINTER_FAKE_ROTATION.getBooleanValue()) { + placeTrapDoor(stateSchematic, mc, pos); + interact++; + continue; + } + int miliseconds = LitematicaMixinMod.EASY_PLACE_CACHE_TIME.getIntegerValue(); + if (blockSchematic instanceof WallMountedBlock || blockSchematic instanceof TorchBlock || blockSchematic instanceof WallSkullBlock + || blockSchematic instanceof LadderBlock + || blockSchematic instanceof TripwireHookBlock || blockSchematic instanceof WallSignBlock || + blockSchematic instanceof EndRodBlock || blockSchematic instanceof DeadCoralFanBlock) { + + /* + * Some blocks, especially wall mounted blocks must be placed on another for + * directionality to work Basically, the block pos sent must be a "clicked" + * block. + */ + if (blockSchematic instanceof ButtonBlock || blockSchematic instanceof LeverBlock) { + BlockFace wallMountLocation = stateSchematic.get(WallMountedBlock.FACE); + if (wallMountLocation == BlockFace.FLOOR) { + npos = pos.down(); + } else if (wallMountLocation == BlockFace.CEILING) { + npos = pos.up(); + } else { + npos = pos.offset(stateSchematic.get(WallMountedBlock.FACING).getOpposite()); + } + } else if (blockSchematic instanceof TorchBlock) { + if (blockSchematic instanceof WallTorchBlock || blockSchematic instanceof WallRedstoneTorchBlock) { + npos = pos.offset(stateSchematic.get(WallTorchBlock.FACING).getOpposite()); + } else { + npos = pos.down(); + } + if (hasGui(world.getBlockState(npos).getBlock())) { + if (LitematicaMixinMod.PRINTER_FAKE_ROTATION.getBooleanValue() && interact < maxInteract) { + if (FakeAccurateBlockPlacement.request(stateSchematic, pos)) { + interact++; + } + continue; + } + recordCause(pos, "Torch at " + pos.toShortString() + " can't be placed due to " + world.getBlockState(npos).getBlock().getTranslationKey() + "at " + npos.toShortString() + " has GUI", npos); + MessageHolder.sendUniqueMessage(mc.player, "Torch at " + pos.toShortString() + " can't be placed due to " + world.getBlockState(npos).getBlock().getTranslationKey() + "at " + npos.toShortString() + " has GUI"); + continue; + } + } else if (blockSchematic instanceof DeadCoralFanBlock) { + if (blockSchematic instanceof DeadCoralWallFanBlock) { + npos = pos.offset(stateSchematic.get(DeadCoralWallFanBlock.FACING).getOpposite()); + } else { + npos = pos.down(); + } + } else { + npos = pos.offset(side.getOpposite()); //offset block for 'side' + } + //Any : if we have block in testPos, then we can place with wanted direction. + //Trapdoors : it can be placed in air with player direction's opposite. + //Else : can't be placed except End Rod. + if (!BedrockBreaker.isReplaceable(mc.world.getBlockState(npos))) { + //npos is blockPos to be hit. + //instead, hitVec should have 1 corresponding to direction property. + //but First check if its block with GUI* + Block checkGui = mc.world.getBlockState(npos).getBlock(); + if (!mc.player.shouldCancelInteraction() && hasGui(checkGui)) { + if (blockSchematic instanceof TrapdoorBlock && LitematicaMixinMod.PRINTER_FAKE_ROTATION.getBooleanValue() && interact < maxInteract) { + if (FakeAccurateBlockPlacement.request(stateSchematic, pos)) { + interact++; + } + continue; + } + //Has GUI so clickPos can't be clicked. + recordCause(pos, stateSchematic.getBlock().getTranslationKey() + " can't be placed at " + pos.toShortString() + "because " + npos.toShortString() + " has GUI", npos); + MessageHolder.sendUniqueMessage(mc.player, getReason(pos.asLong())); + continue; + } else if (blockSchematic instanceof TorchBlock) { + //no gui, just place + if (blockSchematic instanceof WallTorchBlock || blockSchematic instanceof WallRedstoneTorchBlock) { + MessageHolder.sendDebugMessage(mc.player, "placing wall torch clicking " + npos.toShortString() + " torch facing : " + stateSchematic.get(WallTorchBlock.FACING).toString()); + Vec3d hitVec = Vec3d.ofCenter(npos).add(Vec3d.of(stateSchematic.get(WallTorchBlock.FACING).getVector()).multiply(0.5)); + if (stateSchematic.contains(RedstoneTorchBlock.LIT) && !stateSchematic.get(RedstoneTorchBlock.LIT)) { + cacheEasyPlacePosition(pos.up(), false, miliseconds); + } + Direction required = stateSchematic.get(WallTorchBlock.FACING); + if (doSchematicWorldPickBlock(mc, stateSchematic, pos)) { + cacheEasyPlacePosition(pos, false); + interact++; + if (stateSchematic.contains(RedstoneTorchBlock.LIT) && !stateSchematic.get(RedstoneTorchBlock.LIT)) { + cacheEasyPlacePosition(pos.up(), false, miliseconds); + } + BedrockBreaker.interactBlock(mc, new BlockHitResult(hitVec, required, npos, false)); //place block + io.github.eatmyvenom.litematicin.utils.InventoryUtils.decrementCount(isCreative); + sleepWhenRequired(mc); + } + continue; + } + Vec3d hitVec = Vec3d.ofCenter(npos).add(Vec3d.of(Direction.UP.getVector()).multiply(0.5)); + if (doSchematicWorldPickBlock(mc, stateSchematic, pos)) { + MessageHolder.sendDebugMessage(mc.player, "Placing torch clicking " + npos.toShortString()); + MessageHolder.sendDebugMessage(mc.player, "\t Wanted torch pos : " + pos.toShortString()); + MessageHolder.sendDebugMessage(mc.player, "\t HitVec applied : " + hitVec); + MessageHolder.sendDebugMessage(mc.player, "\t Side applied : " + Direction.UP); + cacheEasyPlacePosition(pos, false); + interact++; + if (stateSchematic.contains(RedstoneTorchBlock.LIT) && !stateSchematic.get(RedstoneTorchBlock.LIT)) { + cacheEasyPlacePosition(pos.up(), false, miliseconds); + } + BedrockBreaker.interactBlock(mc, new BlockHitResult(hitVec, Direction.UP, npos, false)); //place block + io.github.eatmyvenom.litematicin.utils.InventoryUtils.decrementCount(isCreative); + sleepWhenRequired(mc); + } + continue; + } else if (canPlaceFace(FacingData.getFacingData(stateSchematic), stateSchematic, primaryFacing, horizontalFacing)) { // no gui + Direction required = fi.dy.masa.malilib.util.BlockUtils.getFirstPropertyFacingValue(stateSchematic); + required = applyPlacementFacing(stateSchematic, required, stateClient); + Vec3d hitVec = applyHitVec(npos, stateSchematic, required); + if (doSchematicWorldPickBlock(mc, stateSchematic, pos)) { + cacheEasyPlacePosition(pos, false); + interact++; + if (stateSchematic.contains(RedstoneTorchBlock.LIT) && !stateSchematic.get(RedstoneTorchBlock.LIT)) { + cacheEasyPlacePosition(pos.up(), false, 700); + } + BedrockBreaker.interactBlock(mc, new BlockHitResult(hitVec, required, npos, false)); //place block + io.github.eatmyvenom.litematicin.utils.InventoryUtils.decrementCount(isCreative); + sleepWhenRequired(mc); + } + continue; + } + } else if (blockSchematic instanceof TrapdoorBlock) { //check direction is opposite of player's + Direction trapdoor = stateSchematic.get(TrapdoorBlock.FACING); + if (horizontalFacing.getOpposite() == trapdoor) { + if (doSchematicWorldPickBlock(mc, stateSchematic, pos)) { + cacheEasyPlacePosition(pos, false); + BedrockBreaker.interactBlock(mc, new BlockHitResult(Vec3d.of(pos), + stateSchematic.get(TrapdoorBlock.FACING).getOpposite(), pos, false)); //place block + io.github.eatmyvenom.litematicin.utils.InventoryUtils.decrementCount(isCreative); + sleepWhenRequired(mc); + interact++; + } + continue; + } + } else if (blockSchematic instanceof GrindstoneBlock) { + Direction direction = stateSchematic.get(GrindstoneBlock.FACING); + if ((primaryFacing.getAxis() == Direction.Axis.Y && horizontalFacing == direction) || (primaryFacing.getAxis() != Direction.Axis.Y && horizontalFacing == direction.getOpposite())) { + if (doSchematicWorldPickBlock(mc, stateSchematic, pos)) { + cacheEasyPlacePosition(pos, false); + BedrockBreaker.interactBlock(mc, new BlockHitResult(Vec3d.of(pos), + stateSchematic.get(GrindstoneBlock.FACING).getOpposite(), pos, false)); //place block + io.github.eatmyvenom.litematicin.utils.InventoryUtils.decrementCount(isCreative); + sleepWhenRequired(mc); + interact++; + } + } + continue; + } else { //Only end rod. + if (blockSchematic instanceof EndRodBlock) { + if (doSchematicWorldPickBlock(mc, stateSchematic, pos)) { + cacheEasyPlacePosition(pos, false); + BedrockBreaker.interactBlock(mc, new BlockHitResult(Vec3d.ofCenter(pos), + stateSchematic.get(EndRodBlock.FACING), pos, false)); //place block + io.github.eatmyvenom.litematicin.utils.InventoryUtils.decrementCount(isCreative); + interact++; + sleepWhenRequired(mc); + } + } + continue; + } + + } //End of trapdoor / wall mounted blocks + + Vec3d hitPos; + // Carpet Accurate Placement protocol support, plus BlockSlab support + if (CanUseProtocol && IsBlockSupportedCarpet(stateSchematic.getBlock())) { + hitPos = applyCarpetProtocolHitVec(npos, stateSchematic); + } else { + hitPos = applyHitVec(npos, stateSchematic, side); + } + + // Mark that this position has been handled (use the non-offset position that is + // checked above) + BlockHitResult hitResult = new BlockHitResult(hitPos, side, npos, false); + + //System.out.printf("pos: %s side: %s, hit: %s\n", pos, side, hitPos); + // pos, side, hitPos + if (stateSchematic.getBlock() instanceof SnowBlock) { + stateClient = mc.world.getBlockState(npos); + if (stateClient.isAir() || stateClient.getBlock() instanceof SnowBlock + && stateClient.get(SnowBlock.LAYERS) < stateSchematic.get(SnowBlock.LAYERS)) { + side = Direction.UP; + hitResult = new BlockHitResult(hitPos, side, npos, false); + if (doSchematicWorldPickBlock(mc, stateSchematic, pos)) { + cacheEasyPlacePosition(pos, false); + interact++; + BedrockBreaker.interactBlock(mc, hitResult); //SNOW LAYERS + io.github.eatmyvenom.litematicin.utils.InventoryUtils.decrementCount(isCreative); + sleepWhenRequired(mc); + } + } + continue; + } + //finally places block + if (smartRedstone) { + if (stateSchematic.contains(RedstoneTorchBlock.LIT) && !stateSchematic.get(RedstoneTorchBlock.LIT)) { + cacheEasyPlacePosition(pos.up(), false, 700); + } + Set shouldCache = ObserverCantAvoidPos(mc, world, pos); + if (!shouldCache.isEmpty()) { + shouldCache.forEach(a -> { + MessageHolder.sendDebugMessage("Caching position " + a.toShortString() + " because observer can't avoid "); + cacheEasyPlacePosition(a, true, (int) Math.ceil(Math.sqrt(a.getSquaredDistance(pos)) * 100)); + }); + } + } + if (!LitematicaMixinMod.PRINTER_FAKE_ROTATION.getBooleanValue() || LitematicaMixinMod.PRINTER_ACCURATE_BLOCK_PLACEMENT.getBooleanValue()) { //Accurateblockplacement, or vanilla but no fake + if (doSchematicWorldPickBlock(mc, stateSchematic, pos)) { + MessageHolder.sendOrderMessage("Places block " + blockSchematic + " at " + pos.toShortString()); + BedrockBreaker.interactBlock(mc, hitResult); //PLACE BLOCK + io.github.eatmyvenom.litematicin.utils.InventoryUtils.decrementCount(isCreative); + cacheEasyPlacePosition(pos, false); + sleepWhenRequired(mc); + interact++; + } + continue; + } else { + if (!(sBlock instanceof FluidBlock)) { + if (interact < maxInteract && FakeAccurateBlockPlacement.request(stateSchematic, pos)) { + interact++; + } + } + } + if (stateSchematic.getBlock() instanceof SlabBlock + && stateSchematic.get(SlabBlock.TYPE) == SlabType.DOUBLE) { + stateClient = mc.world.getBlockState(npos); + + if (stateClient.getBlock() instanceof SlabBlock + && stateClient.get(SlabBlock.TYPE) != SlabType.DOUBLE) { + side = applyPlacementFacing(stateSchematic, sideOrig, stateClient); + hitResult = new BlockHitResult(hitPos, side, npos, false); + if (doSchematicWorldPickBlock(mc, stateSchematic, pos)) { + BedrockBreaker.interactBlock(mc, hitResult); //double slab + io.github.eatmyvenom.litematicin.utils.InventoryUtils.decrementCount(isCreative); + cacheEasyPlacePosition(pos, false); + sleepWhenRequired(mc); + interact++; + } + continue; + } + } + if (stateSchematic.getBlock() instanceof SeaPickleBlock + && stateSchematic.get(SeaPickleBlock.PICKLES) > 1) { + stateClient = mc.world.getBlockState(npos); + if (stateClient.getBlock() instanceof SeaPickleBlock + && stateClient.get(SeaPickleBlock.PICKLES) < stateSchematic.get(SeaPickleBlock.PICKLES)) { + side = applyPlacementFacing(stateSchematic, sideOrig, stateClient); + hitResult = new BlockHitResult(hitPos, side, npos, false); + if (doSchematicWorldPickBlock(mc, stateSchematic, pos)) { + BedrockBreaker.interactBlock(mc, hitResult); //double slab + io.github.eatmyvenom.litematicin.utils.InventoryUtils.decrementCount(isCreative); + cacheEasyPlacePosition(pos, false); + sleepWhenRequired(mc); + interact++; + } + continue; + } + } + + if (interact >= maxInteract) { + if (shouldSleepLonger) { + shouldSleepLonger = false; + lastPlaced = Math.max(lastPlaced, new Date().getTime() + LitematicaMixinMod.PRINTER_SLEEP_STACK_EMPTIED.getIntegerValue()); + } else { + lastPlaced = Math.max(lastPlaced, new Date().getTime()); + } + return ActionResult.SUCCESS; + } + + } else { + MessageHolder.sendUniqueMessage(mc.player, sBlock.getTranslationKey() + " can't be picked !!"); + } + } + } + + } + + if (interact > 0) { + if (shouldSleepLonger) { + shouldSleepLonger = false; + lastPlaced = Math.max(lastPlaced, new Date().getTime() + LitematicaMixinMod.PRINTER_SLEEP_STACK_EMPTIED.getIntegerValue()); + } else { + lastPlaced = Math.max(lastPlaced, new Date().getTime()); + } + return ActionResult.SUCCESS; + } + if (!(mc.player.getMainHandStack().getItem() instanceof BlockItem) && !(mc.player.getOffHandStack().getItem() instanceof BlockItem)) { + return ActionResult.PASS; + } + return ActionResult.FAIL; + } + + private static void markObserverOutputs(MinecraftClient mc, World world, BlockPos pos) { + // Caches all observer outputs + // using cacheEasyPlacePosition to cache the position + // default : 400ms + BlockPos observerOutput = pos.offset(world.getBlockState(pos).get(ObserverBlock.FACING)); + BlockPos observerOutputDown = observerOutput.down(); + cacheEasyPlacePosition(observerOutput, false, 400); + cacheEasyPlacePosition(observerOutputDown, false, 400); + } + + private static boolean checkObserverOutputs(MinecraftClient mc, World world, BlockPos pos) { + // check observer's behind, and its behind's down + BlockState observerState = world.getBlockState(pos); + Direction facing = observerState.get(ObserverBlock.FACING); + BlockPos behindPos = pos.offset(facing.getOpposite()); + BlockPos behindDownPos = behindPos.down(); + // check client world, if both are air, then it's safe to place + BlockState behindStateClient = mc.world.getBlockState(behindPos); + BlockState behindDownStateClient = mc.world.getBlockState(behindDownPos); + if (behindStateClient.isAir() && behindDownStateClient.isAir()) { + return true; + } + // if block behind is powerable block in client world, we have to check recursively + if (!behindStateClient.isTransparent(world, behindPos)) { + return false; + } + // if block behind-down is QCable, we have to check recursively + if (behindDownStateClient.isOf(Blocks.PISTON) || behindDownStateClient.isOf(Blocks.STICKY_PISTON)) { + return false; + } + return true; + } + + private static boolean willFall(BlockState stateSchematic, World clientWorld, BlockPos pos) { + if (stateSchematic.getBlock() instanceof ScaffoldingBlock) { + //return !stateSchematic.getBlock().canPlaceAt(stateSchematic, clientWorld, pos); + return !stateSchematic.canPlaceAt(clientWorld, pos); + } + return false; + } + + /* + returns if redstone block should not be placed (before piston) + */ + private static boolean isQCable(MinecraftClient mc, World schematicWorld, BlockPos pos) { + BlockPos downPos = pos.down(); + for (Direction direction : BedrockBreaker.HORIZONTAL) { + BlockPos offsetPos = downPos.offset(direction); + BlockState stateClient = mc.world.getBlockState(offsetPos); + BlockState stateSchematic = schematicWorld.getBlockState(offsetPos); + if (!(stateSchematic.getBlock() instanceof PistonBlock)) { + continue; + } + if (stateSchematic.get(PistonBlock.EXTENDED)) { + continue; + } + if (stateClient.isAir()) { //very basic qc + return true; + } else if (!hasNoUpdatableState(mc, schematicWorld, offsetPos)) { + return true; + } else if (stateClient.getBlock() instanceof PistonBlock && stateSchematic.get(PistonBlock.FACING).equals(Direction.UP)) { + if (!schematicWorld.getBlockState(offsetPos.up()).getBlock().equals(mc.world.getBlockState(offsetPos.up()).getBlock())) { + return true; + } + } + } + BlockState stateSchematic = schematicWorld.getBlockState(downPos.down()); + if (!isPositionWithinBox(downPos)) { + return false; + } + return stateSchematic.getBlock() instanceof PistonBlock && !stateSchematic.get(PistonBlock.EXTENDED) && !schematicWorld.getBlockState(downPos).getBlock().equals(mc.world.getBlockState(downPos).getBlock()); + } + + private static BlockPos isQCablePos(MinecraftClient mc, World world, BlockPos pos) { + BlockPos downPos = pos.down(); + for (Direction direction : BedrockBreaker.HORIZONTAL) { + BlockPos offsetPos = downPos.offset(direction); + BlockState stateClient = mc.world.getBlockState(offsetPos); + BlockState stateSchematic = world.getBlockState(offsetPos); + if (!(stateSchematic.getBlock() instanceof PistonBlock)) { + continue; + } + if (stateSchematic.get(PistonBlock.EXTENDED)) { + continue; + } + if (stateClient.isAir()) { //very basic qc + return offsetPos; + } else if (!hasNoUpdatableState(mc, world, offsetPos)) { + return hasNoUpdatableStatePos(mc, world, offsetPos); + } else if (stateClient.getBlock() instanceof PistonBlock && stateSchematic.get(PistonBlock.FACING).equals(Direction.UP)) { + if (!world.getBlockState(offsetPos.up()).getBlock().equals(mc.world.getBlockState(offsetPos.up()).getBlock())) { + return offsetPos; + } + } + } + BlockState stateSchematic = world.getBlockState(downPos.down()); + if (!isPositionWithinBox(downPos)) { + return null; + } + if (stateSchematic.getBlock() instanceof PistonBlock && !stateSchematic.get(PistonBlock.EXTENDED) && !world.getBlockState(downPos).getBlock().equals(mc.world.getBlockState(downPos).getBlock())) { + return downPos; + } + return null; + } + + private static boolean hasNoUpdatableState(MinecraftClient mc, World world, BlockPos pos) { + for (Direction direction : Direction.values()) { + BlockPos offsetPos = pos.offset(direction); + if (world.getBlockState(offsetPos) != mc.world.getBlockState(offsetPos)) { + if (!isNoteBlockInstrumentError(mc, world, offsetPos) && !isDoorHingeError(mc, world, offsetPos)) { + if (!isPositionWithinBox(offsetPos) || world.isAir(offsetPos) && mc.world.isAir(offsetPos)) { + continue; + } + return false; + } + } + } + return true; + } + + private static BlockPos hasNoUpdatableStatePos(MinecraftClient mc, World world, BlockPos pos) { + for (Direction direction : Direction.values()) { + BlockPos offsetPos = pos.offset(direction); + if (world.getBlockState(offsetPos) != mc.world.getBlockState(offsetPos)) { + if (!isNoteBlockInstrumentError(mc, world, offsetPos) && !isDoorHingeError(mc, world, offsetPos)) { + if (!isPositionWithinBox(offsetPos) || world.isAir(offsetPos) && mc.world.isAir(offsetPos)) { + continue; + } + return offsetPos; + } + } + } + return null; + } + + private static boolean hasNearbyRedirectDust(MinecraftClient mc, World world, BlockPos pos) { //temporary code, just direct redirection check nearby + for (Direction direction : Direction.values()) { + if (!isPositionWithinBox(pos.offset(direction))) { + continue; + } + if (!isCorrectDustState(mc, world, pos.offset(direction))) { + return true; + } + if (direction.getAxis() != Direction.Axis.Y && !isCorrectDustState(mc, world, pos.offset(direction, 2))) { + return true; + } + if (!isCorrectDustState(mc, world, pos.offset(direction).up())) { + return true; + } + if (direction.getAxis() != Direction.Axis.Y && !isCorrectDustState(mc, world, pos.offset(direction, 2).up())) { + return true; + } + } + return false; + } + + private static BlockPos hasNearbyRedirectDustPos(MinecraftClient mc, World world, BlockPos pos) { //temporary code, just direct redirection check nearby + for (Direction direction : Direction.values()) { + if (!isPositionWithinBox(pos.offset(direction))) { + continue; + } + if (!isCorrectDustState(mc, world, pos.offset(direction))) { + return pos.offset(direction); + } + if (!isCorrectDustState(mc, world, pos.offset(direction, 2))) { + return pos.offset(direction, 2); + } + if (!isCorrectDustState(mc, world, pos.offset(direction).up())) { + return pos.offset(direction).up(); + } + if (!isCorrectDustState(mc, world, pos.offset(direction, 2).up())) { + return pos.offset(direction, 2).up(); + } + } + return null; + } + + private static boolean cantAvoidExtend(World world, BlockPos pos, World schematicWorld) { + if (!schematicWorld.getBlockState(pos).get(PistonBlock.EXTENDED)) { + return willExtendInWorld(world, pos, schematicWorld.getBlockState(pos).get(PistonBlock.FACING)); + } + return false; + } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + private static boolean isCorrectDustState(MinecraftClient mc, World world, BlockPos pos) { + BlockState ClientState = mc.world.getBlockState(pos); + BlockState SchematicState = world.getBlockState(pos); + if (!SchematicState.isOf(Blocks.REDSTONE_WIRE)) { + return true; + } + if (!ClientState.isOf(Blocks.REDSTONE_WIRE)) { + return false; + } + return SchematicState.get(RedstoneWireBlock.WIRE_CONNECTION_EAST) == ClientState.get(RedstoneWireBlock.WIRE_CONNECTION_EAST) && + SchematicState.get(RedstoneWireBlock.WIRE_CONNECTION_WEST) == ClientState.get(RedstoneWireBlock.WIRE_CONNECTION_WEST) && + SchematicState.get(RedstoneWireBlock.WIRE_CONNECTION_SOUTH) == ClientState.get(RedstoneWireBlock.WIRE_CONNECTION_SOUTH) && + SchematicState.get(RedstoneWireBlock.WIRE_CONNECTION_NORTH) == ClientState.get(RedstoneWireBlock.WIRE_CONNECTION_NORTH) && + Objects.equals(SchematicState.get(RedstoneWireBlock.POWER) == 0, ClientState.get(RedstoneWireBlock.POWER) == 0); + } + + private static boolean shouldExtendQC(MinecraftClient mc, World world, BlockPos pos) { + return willExtendInWorld(mc.world, pos, world.getBlockState(pos).get(PistonBlock.FACING)) == world.getBlockState(pos).get(PistonBlock.EXTENDED); + } + + /* + returns if piston is powered + but it's not extended in schematic, can be BUD or direct power + */ + private static boolean shouldSuppressExtend(World world, BlockPos pos) { + return willExtendInWorld(world, pos, world.getBlockState(pos).get(PistonBlock.FACING)) && !world.getBlockState(pos).get(PistonBlock.EXTENDED); + } + + /* + returns if piston is DIRECTLY powered by redstone block, can't be solved via QC references. + */ + private static boolean directlyPowered(World schematicWorld, BlockPos pos, Direction pistonFace) { + for (Direction lv : Direction.values()) { + if (lv == pistonFace) { + continue; + } + if (schematicWorld.getBlockState(pos.offset(lv)).isOf(Blocks.REDSTONE_BLOCK)) { + return true; + } + } + return false; + } + + private static boolean willExtendInWorld(World world, BlockPos pos, Direction pistonFace) { + for (Direction lv : Direction.values()) { + if (lv == pistonFace || !world.isEmittingRedstonePower(pos.offset(lv), lv)) { + continue; + } + //Observer client error wtf? + boolean hasObserver = false; + for (Direction dir : Direction.values()) { + BlockState observerState = world.getBlockState(pos.offset(lv).offset(dir)); + if (observerState.isOf(Blocks.OBSERVER)) { + if (observerState.get(ObserverBlock.POWERED)) { + hasObserver = true; + break; + } + } + } + BlockState adjState = world.getBlockState(pos.offset(lv)); + if (adjState.isOf(Blocks.OBSERVER)) { + if (adjState.get(ObserverBlock.POWERED)) { + hasObserver = true; + } + } + if (hasObserver) { + continue; + } + return true; + } + if (world.isEmittingRedstonePower(pos, Direction.DOWN)) { + return true; + } + BlockPos lv2 = pos.up(); + for (Direction lv3 : Direction.values()) { + if (lv3 == Direction.DOWN || !world.isEmittingRedstonePower(lv2.offset(lv3), lv3)) { + continue; + } + BlockState qcState = world.getBlockState(lv2.offset(lv3)); + if (qcState.isOf(Blocks.OBSERVER) && qcState.get(ObserverBlock.FACING) == lv3 && qcState.get(ObserverBlock.POWERED)) { + continue; + } + return true; + } + return false; + } + + /* * * + returns if block is observer output but observer can't avoid update + If its true, then block should be placed after observer update is done + Case A : Observer is facing wall attached : observer - wall - output + Case B : Observer is facing Noteblock from horizontal : observer - block below noteblock - noteblock - output + Case C : Observer is facing wire connected to observer's up offset + * * */ + @SuppressWarnings({"ConstantConditions"}) + private static BlockPos isObserverCantAvoidOutput(MinecraftClient mc, World schematicWorld, BlockPos pos) { + if (isQCableBlock(schematicWorld.getBlockState(pos))) { + if (schematicWorld.getBlockState(pos.up(2)).isOf(Blocks.OBSERVER) && schematicWorld.getBlockState(pos.up(2)).get(ObserverBlock.FACING) == Direction.UP) { + if (mc.world.getBlockState(pos.up(3)) != schematicWorld.getBlockState(pos.up(3)) || mc.world.getBlockState(pos.up(2)).contains(ObserverBlock.POWERED) && mc.world.getBlockState(pos.up(2)).get(ObserverBlock.POWERED)) { + MessageHolder.sendDebugMessage("Position at " + pos.toShortString() + " has observer that will QC, but not watching correct state"); + return pos.up(3); + } + } + } + for (Direction direction : Direction.values()) { + BlockState offsetState = schematicWorld.getBlockState(pos.offset(direction)); + if (offsetState.getBlock() instanceof ObserverBlock && offsetState.get(ObserverBlock.FACING) == direction) { + Pair value = isWatchingCorrectState(mc, schematicWorld, pos.offset(direction), null, false); + if (!value.getLeft()) { + return pos.offset(direction); + } + } + //QC + if (direction == Direction.UP || direction == Direction.DOWN || !isQCableBlock(schematicWorld, pos)) { + continue; + } + //Horizontal, + BlockPos qcPos = pos.offset(direction).up(); + BlockState qcState = schematicWorld.getBlockState(qcPos); + BlockState existingState = mc.world.getBlockState(qcPos); + if (qcState.getBlock() instanceof ObserverBlock && !existingState.isOf(Blocks.OBSERVER) && qcState.get(ObserverBlock.FACING) == direction) { + Pair value = isWatchingCorrectState(mc, schematicWorld, qcPos, null, false); + if (!value.getLeft()) { + return pos.offset(direction); + } + } + // again, QC + powerable block uhh + else if (qcState.isSolidBlock(schematicWorld, qcPos)) { + qcPos = qcPos.offset(direction); + qcState = schematicWorld.getBlockState(qcPos); + if (qcState.getBlock() instanceof ObserverBlock && !existingState.isOf(Blocks.OBSERVER) && qcState.get(ObserverBlock.FACING) == direction) { + Pair value = isWatchingCorrectState(mc, schematicWorld, qcPos, null, false); + if (!value.getLeft()) { + return pos.offset(direction); + } + } + } + } + return null; + } + + private static void sleepWhenRequired(MinecraftClient mc) { + if (!LitematicaMixinMod.USE_INVENTORY_CACHE.getBooleanValue()) { + return; + } + if (LitematicaMixinMod.PRINTER_SLEEP_STACK_EMPTIED.getIntegerValue() > 0 && io.github.eatmyvenom.litematicin.utils.InventoryUtils.lastCount <= 0) { + shouldSleepLonger = true; + lastPlaced = new Date().getTime() + LitematicaMixinMod.PRINTER_SLEEP_STACK_EMPTIED.getIntegerValue(); + MessageHolder.sendUniqueMessageActionBar(mc.player, "Sleeping because stack is emptied!"); + isSleeping = true; + } + } + + private static boolean isQCableBlock(World world, BlockPos pos) { + Block block = world.getBlockState(pos).getBlock(); + return (!LitematicaMixinMod.PRINTER_AVOID_CHECK_ONLY_PISTONS.getBooleanValue() && block instanceof DispenserBlock) || block instanceof PistonBlock; + } + + private static boolean isQCableBlock(BlockState blockState) { + Block block = blockState.getBlock(); + return (!LitematicaMixinMod.PRINTER_AVOID_CHECK_ONLY_PISTONS.getBooleanValue() && block instanceof DispenserBlock) || block instanceof PistonBlock; + } + + /*** + * + * @param mc : client + * @param schematicWorld : schematic world + * @param pos : BlockPos + * @param recursive : Sets of position checked + * @param allowFirst : direct search of wallmount / walls / etc. at first + * @return Entry : correct / position caused + */ + private static Pair isWatchingCorrectState(MinecraftClient mc, World schematicWorld, BlockPos pos, Set recursive, boolean allowFirst) { + //observer, then recursive + if (recursive == null) { + recursive = new HashSet<>(); + } + if (recursive.contains(pos.asLong())) { + return new Pair<>(true, pos); + } + BlockState clientState = mc.world.getBlockState(pos); + BlockState schematicState = schematicWorld.getBlockState(pos); + if (schematicState.getBlock() instanceof ObserverBlock) { + Direction facing = schematicState.get(ObserverBlock.FACING); + recursive.add(pos.asLong()); + if (allowFirst && ObserverCantAvoid(mc, schematicWorld, facing, pos)) { + return new Pair<>(true, pos); + } else { + Pair entry = isWatchingCorrectState(mc, schematicWorld, pos.offset(facing), recursive, allowFirst); + if (entry.getLeft()) { + return entry; + } else { + return new Pair<>(false, pos); + } + } + }// virtual observers then go recursive + else { + if (schematicState == Blocks.VOID_AIR.getDefaultState() || schematicState == Blocks.BARRIER.getDefaultState() || schematicState.isAir()) { + return new Pair<>(true, pos); + } else if (clientState != schematicState) { + //but check wire... + if (isNoteBlockInstrumentError(mc, schematicWorld, pos) || isDoorHingeError(mc, schematicWorld, pos)) { + return new Pair<>(true, pos); + } + if (isClientPowerError(mc, schematicWorld, clientState, schematicState, pos)) { + return new Pair<>(true, pos); + } + return new Pair<>(false, pos); + } + } + return new Pair<>(true, pos); + } + + private static boolean isClientPowerError(MinecraftClient mc, World world, BlockState clientState, BlockState schematicState, BlockPos pos) { + //handles client error, mostly, dropper being powered directly, hopper being powered etc + if (clientState.getBlock() != schematicState.getBlock()) { + return false; + } + if (schematicState.isOf(Blocks.DROPPER) || schematicState.isOf(Blocks.DISPENSER)) { + if (schematicState.get(DropperBlock.TRIGGERED) != clientState.get(DropperBlock.TRIGGERED)) { + boolean isReceiving = mc.world.isReceivingRedstonePower(pos) || mc.world.isReceivingRedstonePower(pos.up()); + if (isReceiving != clientState.get(DropperBlock.TRIGGERED)) { + mc.world.setBlockState(pos, clientState.with(DropperBlock.TRIGGERED, isReceiving)); + } + return mc.world.getBlockState(pos) == schematicState; + } + } else if (schematicState.isOf(Blocks.NOTE_BLOCK)) { //special case + if (schematicState.get(NoteBlock.POWERED) != clientState.get(NoteBlock.POWERED)) { + boolean isReceiving = mc.world.isReceivingRedstonePower(pos); + mc.world.setBlockState(pos, clientState.with(NoteBlock.POWERED, isReceiving)); //lets fix + return isNoteBlockInstrumentError(mc, world, pos); + } + } else if (schematicState.isOf(Blocks.HOPPER)) { + if (schematicState.get(HopperBlock.ENABLED) != clientState.get(HopperBlock.ENABLED)) { + boolean isReceiving = mc.world.isReceivingRedstonePower(pos); + mc.world.setBlockState(pos, clientState.with(HopperBlock.ENABLED, !isReceiving)); + return mc.world.getBlockState(pos) == schematicState; + } + } + return false; + } + + private static boolean ObserverCantAvoid(MinecraftClient mc, World world, Direction facingSchematic, BlockPos pos) { + //returns true if observer should be placed regardless of state + BlockPos posOffset = pos.offset(facingSchematic); + BlockState OffsetStateSchematic = world.getBlockState(posOffset); + Block offsetBlock = OffsetStateSchematic.getBlock(); + if (OffsetStateSchematic.isOf(Blocks.NOTE_BLOCK)) { + if (isNoteBlockInstrumentError(mc, world, posOffset) || isDoorHingeError(mc, world, posOffset)) { + //everything is correct but litematica error + return true; + } + } + if (facingSchematic.equals(Direction.UP)) { + return offsetBlock instanceof WallBlock || offsetBlock instanceof ComparatorBlock || offsetBlock instanceof DoorBlock || + offsetBlock instanceof RepeaterBlock || offsetBlock instanceof FallingBlock || + offsetBlock instanceof AbstractRailBlock || offsetBlock instanceof NoteBlock || + offsetBlock instanceof BubbleColumnBlock || offsetBlock instanceof RedstoneWireBlock || + ((offsetBlock instanceof WallMountedBlock) && OffsetStateSchematic.get(WallMountedBlock.FACE) == BlockFace.FLOOR); + } else if (facingSchematic.equals(Direction.DOWN)) { + return offsetBlock instanceof WallBlock || offsetBlock instanceof WallMountedBlock && OffsetStateSchematic.get(WallMountedBlock.FACE) == BlockFace.CEILING; + } else { + return offsetBlock instanceof WallBlock || offsetBlock instanceof PaneBlock || offsetBlock instanceof FenceBlock || OffsetStateSchematic.isOf(Blocks.IRON_BARS) || offsetBlock instanceof WallMountedBlock && + OffsetStateSchematic.get(WallMountedBlock.FACE) == BlockFace.WALL && OffsetStateSchematic.get(WallMountedBlock.FACING) == facingSchematic || hasDustOrAscendingRails(world, facingSchematic, pos); + } + } + + private static boolean hasDustOrAscendingRails(World schematicWorld, Direction watching, BlockPos observerPos) { + BlockPos possible = observerPos.offset(watching); + BlockState state = schematicWorld.getBlockState(possible); + if (state.isOf(Blocks.REDSTONE_WIRE)) { + //ascending_'opposite' directions + //watching.getOpposite should have 'up' + WireConnection connection = state.get(RedstoneWireBlock.DIRECTION_TO_WIRE_CONNECTION_PROPERTY.get(watching.getOpposite())); + return connection == WireConnection.UP; + + } else if (state.getBlock() instanceof PoweredRailBlock) { + switch (watching) { + case Direction.NORTH : { + return state.get(PoweredRailBlock.SHAPE) == RailShape.ASCENDING_SOUTH; + } + case Direction.SOUTH : { + return state.get(PoweredRailBlock.SHAPE) == RailShape.ASCENDING_NORTH; + } + case Direction.EAST : { + return state.get(PoweredRailBlock.SHAPE) == RailShape.ASCENDING_WEST; + } + case Direction.WEST : { + return state.get(PoweredRailBlock.SHAPE) == RailShape.ASCENDING_EAST; + } + default : { + return false; + } + } + } + return false; + } + + private static List getNeighborsExcept(BlockPos pos, Direction except) { + List retVal = new ArrayList<>(5); + for (Direction direction : Direction.values()) { + if (direction == except.getOpposite()) { + continue; + } + retVal.add(pos.offset(direction)); + } + return retVal; + } + + /* + if block is observer updating block, then return position to not place until its finished + */ + private static Set ObserverCantAvoidPos(MinecraftClient mc, World world, BlockPos pos) { + //returns true if observer should be placed regardless of state + BlockPos posOffset; + Set relatedPos = new HashSet<>(6); + BlockState offsetStateSchematic; + BlockState targetState = world.getBlockState(pos); + Block block = targetState.getBlock(); + if (block instanceof ComparatorBlock || block instanceof RepeaterBlock || block instanceof FallingBlock || + block instanceof AbstractRailBlock || block instanceof RedstoneWireBlock || block instanceof DoorBlock || + ((block instanceof WallMountedBlock) && targetState.get(WallMountedBlock.FACE) == BlockFace.FLOOR)) { + //check downward + if (world.getBlockState(pos.down()).isOf(Blocks.OBSERVER) && world.getBlockState(pos.down()).get(ObserverBlock.FACING) == Direction.UP) { + relatedPos.add(pos.down(2)); + relatedPos.addAll(getNeighborsExcept(pos, Direction.DOWN)); + if (world.getBlockState(pos.down(3)).getBlock() instanceof PistonBlock) { + relatedPos.add(pos.down(3)); + } + return relatedPos; + } + } + if (block instanceof NoteBlock) { + if (!isNoteBlockInstrumentError(mc, world, pos)) { + relatedPos.add(pos.down(2)); + relatedPos.addAll(getNeighborsExcept(pos, Direction.DOWN)); + if (world.getBlockState(pos.down(3)).getBlock() instanceof PistonBlock) { + relatedPos.add(pos.down(3)); + } + return relatedPos; + } + } else if (block instanceof DoorBlock) { + if (!isDoorHingeError(mc, world, pos)) { + relatedPos.add(pos.down(2)); + relatedPos.addAll(getNeighborsExcept(pos, Direction.DOWN)); + if (world.getBlockState(pos.down(3)).getBlock() instanceof PistonBlock) { + relatedPos.add(pos.down(3)); + } + return relatedPos; + } + } + for (Direction direction : Direction.values()) { + posOffset = pos.offset(direction); + offsetStateSchematic = world.getBlockState(posOffset); + if (offsetStateSchematic.isOf(Blocks.OBSERVER) && offsetStateSchematic.get(ObserverBlock.FACING) == direction.getOpposite()) { + if (block instanceof WallBlock) { + relatedPos.add(pos.offset(direction, 2)); + relatedPos.addAll(getNeighborsExcept(pos, direction)); + if (world.getBlockState(pos.offset(direction, 2).down()).getBlock() instanceof PistonBlock) { + relatedPos.add(pos.offset(direction, 2).down()); + } + } else if (block instanceof WallMountedBlock) { + if (targetState.get(WallMountedBlock.FACE) == BlockFace.CEILING && direction == Direction.UP || + targetState.get(WallMountedBlock.FACE) == BlockFace.FLOOR && direction == Direction.DOWN || + targetState.get(WallMountedBlock.FACE) == BlockFace.WALL && targetState.get(WallMountedBlock.FACING) == direction.getOpposite() + ) { + relatedPos.add(pos.offset(direction, 2)); + relatedPos.addAll(getNeighborsExcept(pos, direction)); + if (world.getBlockState(pos.offset(direction, 2).down()).getBlock() instanceof PistonBlock) { + relatedPos.add(pos.offset(direction, 2).down()); + } + } + } else if (block instanceof PoweredRailBlock || block instanceof RedstoneWireBlock) { + if (hasDustOrAscendingRails(world, direction.getOpposite(), pos)) { + relatedPos.add(pos.offset(direction, 2)); + relatedPos.addAll(getNeighborsExcept(pos, direction)); + } + } + } + } + return relatedPos; + } + + + private static boolean shouldAvoidPlaceCart(BlockPos pos, World schematicWorld) { + //avoids TNT priming + for (Direction direction : Direction.values()) { + if (schematicWorld.getBlockState(pos.down().offset(direction)).isOf(Blocks.TNT)) { + return true; + } + } + return false; + } + + // returns should call continue in loop + @SuppressWarnings({"ConstantConditions"}) + private static boolean placeCart(BlockState state, MinecraftClient client, BlockPos pos) { + if (state.isOf(Blocks.DETECTOR_RAIL) && state.get(DetectorRailBlock.POWERED) != client.world.getBlockState(pos).get(DetectorRailBlock.POWERED) && canPickItem(client, Items.MINECART.getDefaultStack()) && client.player.getPos().distanceTo(Vec3d.of(pos)) < 4.5) { + Vec3d clickPos = Vec3d.of(pos).add(0.5, 0.125, 0.5); + if (!FakeAccurateBlockPlacement.canHandleOther(Items.MINECART)) { + return false; + } + if (doSchematicWorldPickBlock(client, Items.MINECART.getDefaultStack())) { + ActionResult actionResult = BedrockBreaker.interactBlock(client, new BlockHitResult(clickPos, Direction.UP, pos, false)); //place block + if (actionResult.isAccepted()) { + cacheEasyPlacePosition(pos, false, 600); + return true; + } + } + return false; + } + return false; + } + + @SuppressWarnings({"ConstantConditions"}) + private static void placeGrindStone(BlockState state, MinecraftClient client, BlockPos pos) { + if (LitematicaMixinMod.PRINTER_FAKE_ROTATION.getBooleanValue()) { + FakeAccurateBlockPlacement.request(state, pos); + return; + //place in air + } + if (!canAttachGrindstone(state, client, pos) && !isFacingCorrectly(state, client.player)) { + return; + } + Direction side = state.get(GrindstoneBlock.FACING); + BlockFace location = state.get(GrindstoneBlock.FACE); + BlockPos clickPos; + Vec3d hitVec; + if (canAttachGrindstone(state, client, pos)) { + //offset positions + if (location == BlockFace.CEILING) { + clickPos = pos.up(); + } else if (location == BlockFace.FLOOR) { + clickPos = pos.down(); + } else { + clickPos = pos.offset(side.getOpposite()); + } + hitVec = Vec3d.ofCenter(clickPos).add(Vec3d.of(side.getVector()).multiply(0.5)); + if (doSchematicWorldPickBlock(client, state, pos)) { + cacheEasyPlacePosition(pos, false); + BedrockBreaker.interactBlock(client, new BlockHitResult(hitVec, side, clickPos, false)); //place block + } + } else { + if (isFacingCorrectly(state, client.player)) { + hitVec = Vec3d.ofCenter(pos); + clickPos = pos; + if (doSchematicWorldPickBlock(client, state, pos)) { + cacheEasyPlacePosition(pos, false); + BedrockBreaker.interactBlock(client, new BlockHitResult(hitVec, side, clickPos, false)); //place block + } + } + } + } + + @SuppressWarnings({"ConstantConditions"}) + private static boolean canAttachGrindstone(BlockState state, MinecraftClient client, BlockPos pos) { + if (LitematicaMixinMod.PRINTER_FAKE_ROTATION.getBooleanValue()) { + return true; + //place in air. + } + Direction facing = state.get(WallMountedBlock.FACING); + BlockFace location = state.get(WallMountedBlock.FACE); + //case ceil + if (location == BlockFace.CEILING) { + return !BedrockBreaker.isReplaceable(client.world.getBlockState(pos.up())) && client.player.getHorizontalFacing() == facing.getOpposite() && !hasGui(client.world.getBlockState(pos.up()).getBlock()) || client.player.shouldCancelInteraction(); + } else if (location == BlockFace.FLOOR) { + return !BedrockBreaker.isReplaceable(client.world.getBlockState(pos.down())) && client.player.getHorizontalFacing() == facing.getOpposite() && !hasGui(client.world.getBlockState(pos.down()).getBlock()) || client.player.shouldCancelInteraction(); + } else { + return !BedrockBreaker.isReplaceable(client.world.getBlockState(pos.offset(facing.getOpposite()))) && !hasGui(client.world.getBlockState(pos.offset(facing.getOpposite())).getBlock()) || client.player.shouldCancelInteraction(); + } + } + + private static boolean isFacingCorrectly(BlockState state, ClientPlayerEntity player) { + //if we can't attach, use player's directions + Direction facing = state.get(WallMountedBlock.FACING); + BlockFace location = state.get(WallMountedBlock.FACE); + Direction[] facingOrder = Direction.getEntityFacingOrder(player); + if (location == BlockFace.CEILING) { + //primary should be UP + //secondary should be same as facing + return facingOrder[0] == Direction.UP && player.getHorizontalFacing() == facing; + } else if (location == BlockFace.FLOOR) { + return facingOrder[0] == Direction.DOWN && player.getHorizontalFacing() == facing; + } else { + return facingOrder[0] == facing.getOpposite(); + } + } + + private static void placeTrapDoor(BlockState state, MinecraftClient client, BlockPos pos) { + //check if it can be clicked on face, then place inside block + Direction side = state.get(TrapdoorBlock.FACING); + BlockPos clickPos; + Vec3d hitVec; + if (BedrockBreaker.isReplaceable(client.world.getBlockState(pos.offset(side.getOpposite())))) { + //place inside block + clickPos = pos; + if (client.player.getHorizontalFacing().getOpposite() == side) { + side = state.get(TrapdoorBlock.HALF) == BlockHalf.TOP ? Direction.DOWN : Direction.UP; + } else { + return; + } + hitVec = Vec3d.of(clickPos).add(0.5, 0.5, 0.5); + } else { + clickPos = pos.offset(side.getOpposite()); + hitVec = Vec3d.of(clickPos).add(0.5, 0.5, 0.5).add(Vec3d.of(side.getVector()).multiply(0.5)); + } + if (doSchematicWorldPickBlock(client, state, pos)) { + cacheEasyPlacePosition(pos, false); + BedrockBreaker.interactBlock(client, new BlockHitResult(hitVec, side, clickPos, false)); //place block + } + } + + private static boolean isNoteBlockInstrumentError(MinecraftClient mc, World world, BlockPos pos) { + BlockState stateA = world.getBlockState(pos); + BlockState stateB = mc.world.getBlockState(pos); + return stateA.isOf(Blocks.NOTE_BLOCK) && stateB.isOf(Blocks.NOTE_BLOCK) && + stateA.get(NoteBlock.POWERED) == stateB.get(NoteBlock.POWERED) && + BedrockBreaker.isReplaceable(world.getBlockState(pos.down())) == BedrockBreaker.isReplaceable(mc.world.getBlockState(pos.offset(Direction.DOWN))); + } + + private static boolean isDoorHingeError(MinecraftClient mc, World world, BlockPos pos) { + BlockState stateA = world.getBlockState(pos); + BlockState stateB = mc.world.getBlockState(pos); + return stateA.contains(DoorBlock.HINGE) && stateB.contains(DoorBlock.HINGE) && + stateA.get(DoorBlock.POWERED) == stateB.get(DoorBlock.POWERED) && + stateA.get(DoorBlock.FACING) == stateB.get(DoorBlock.FACING) && + stateA.get(DoorBlock.OPEN) == stateB.get(DoorBlock.OPEN) && + stateA.get(DoorBlock.HALF) == stateB.get(DoorBlock.HALF); + } + + private static boolean ObserverUpdateOrder(MinecraftClient mc, World world, BlockPos pos, Box selectedBox) { + //returns true if observer should not be placed + boolean ExplicitObserver = LitematicaMixinMod.PRINTER_OBSERVER_AVOID_ALL.getBooleanValue(); + BlockState stateSchematic = world.getBlockState(pos); + BlockPos posOffset; + BlockState OffsetStateSchematic; + BlockState OffsetStateClient; + if (stateSchematic.get(ObserverBlock.POWERED)) { + return false; + } + Direction facingSchematic = fi.dy.masa.malilib.util.BlockUtils.getFirstPropertyFacingValue(stateSchematic); + assert facingSchematic != null; + boolean observerCantAvoid = ObserverCantAvoid(mc, world, facingSchematic, pos); + if (observerCantAvoid) { + return false; + } + posOffset = pos.offset(facingSchematic); + if (!isPositionWithinBox(selectedBox, posOffset)) { + return false; + } + OffsetStateSchematic = world.getBlockState(posOffset); + OffsetStateClient = mc.world.getBlockState(posOffset); + if (OffsetStateSchematic.isOf(Blocks.BARRIER)) { + return false; + } + if (OffsetStateSchematic.isOf(Blocks.OBSERVER) && OffsetStateSchematic.get(ObserverBlock.FACING) == facingSchematic.getOpposite()) { + return false; + } + if (OffsetStateSchematic.getBlock() instanceof DoorBlock && OffsetStateClient.getBlock() instanceof DoorBlock && + OffsetStateSchematic.get(DoorBlock.POWERED) == OffsetStateClient.get(DoorBlock.POWERED) && + OffsetStateSchematic.get(DoorBlock.FACING) == OffsetStateClient.get(DoorBlock.FACING)) { + return false; + } + if (ExplicitObserver) { + if (OffsetStateSchematic.isOf(Blocks.BARRIER) || OffsetStateClient.isAir() && OffsetStateSchematic.isAir() || OffsetStateSchematic.isOf(Blocks.VOID_AIR)) { + return false; + } //cave air wtf + if (isClientPowerError(mc, world, OffsetStateClient, OffsetStateSchematic, posOffset)) { + return false; + } + return !OffsetStateSchematic.toString().equals(OffsetStateClient.toString()); + } + return !OffsetStateClient.getBlock().equals(OffsetStateSchematic.getBlock()); + } + + private static BlockPos ObserverUpdateOrderPos(MinecraftClient mc, World world, BlockPos pos) { + //returns true if observer should not be placed + boolean ExplicitObserver = LitematicaMixinMod.PRINTER_OBSERVER_AVOID_ALL.getBooleanValue(); + BlockState stateSchematic = world.getBlockState(pos); + BlockPos posOffset; + BlockState OffsetStateSchematic; + BlockState OffsetStateClient; + if (stateSchematic.get(ObserverBlock.POWERED)) { + return null; + } + Direction facingSchematic = fi.dy.masa.malilib.util.BlockUtils.getFirstPropertyFacingValue(stateSchematic); + assert facingSchematic != null; + boolean observerCantAvoid = ObserverCantAvoid(mc, world, facingSchematic, pos); + if (observerCantAvoid) { + return null; + } + posOffset = pos.offset(facingSchematic); + assert pos != posOffset; + OffsetStateSchematic = world.getBlockState(posOffset); + OffsetStateClient = mc.world.getBlockState(posOffset); + if (OffsetStateSchematic.isOf(Blocks.BARRIER)) { + return null; + } else if (OffsetStateSchematic.isOf(Blocks.OBSERVER) && OffsetStateSchematic.get(ObserverBlock.FACING) == facingSchematic.getOpposite()) { + return null; + } else if (OffsetStateSchematic.getBlock() instanceof DoorBlock && OffsetStateClient.getBlock() instanceof DoorBlock && + OffsetStateSchematic.get(DoorBlock.POWERED) == OffsetStateClient.get(DoorBlock.POWERED) && + OffsetStateSchematic.get(DoorBlock.FACING) == OffsetStateClient.get(DoorBlock.FACING)) //hinge error + { + return null; + } + if (ExplicitObserver) { + if (OffsetStateSchematic.isOf(Blocks.BARRIER) || OffsetStateClient.isAir() && OffsetStateSchematic.isAir()) { + return null; + } //cave air wtf + if (!OffsetStateSchematic.toString().equals(OffsetStateClient.toString())) { + if (isClientPowerError(mc, world, OffsetStateClient, OffsetStateSchematic, posOffset)) { + return null; + } + return posOffset; + } + } + + if (!OffsetStateClient.getBlock().equals(OffsetStateSchematic.getBlock())) { + return posOffset; + } + return null; + } + + /* + * Checks if the block can be placed in the correct orientation if player is + * facing a certain direction Don't place block if orientation will be wrong + */ + private static boolean canPlaceFace(FacingData facedata, BlockState stateSchematic, + Direction primaryFacing, Direction horizontalFacing) { + if (stateSchematic.isOf(Blocks.GRINDSTONE)) { + return true; + } + Direction facing = fi.dy.masa.malilib.util.BlockUtils.getFirstPropertyFacingValue(stateSchematic); + if (stateSchematic.getBlock() instanceof AbstractRailBlock) { + facing = convertRailShapetoFace(stateSchematic); + } + if (facing != null && facedata != null) { + // backward compatibility, JAVA_8 can't use enhanced switch + switch (facedata.type) { + case 0: // All directions (ie, observers and pistons) + if (facedata.isReversed) { + return facing.getOpposite() == primaryFacing; + } else { + return facing == primaryFacing; + } + + case 1: // Only Horizontal directions (ie, repeaters and comparators) + if (facedata.isReversed) { + return facing.getOpposite() == horizontalFacing; + } else { + return facing == horizontalFacing; + } + case 2: // Wall mountable, such as a lever, only use player direction if not on wall. + return stateSchematic.get(WallMountedBlock.FACE) == BlockFace.WALL + || (facing == horizontalFacing && stateSchematic.get(WallMountedBlock.FACE) == BlockFace.CEILING ? (primaryFacing == Direction.UP && horizontalFacing == stateSchematic.get(WallMountedBlock.FACING)) : (primaryFacing == Direction.DOWN && horizontalFacing == stateSchematic.get(WallMountedBlock.FACING))); + case 3: //rotated, why, anvil, WNES order + return horizontalFacing.rotateYClockwise() == facing; + case 4: //rails + return facing == horizontalFacing || facing == horizontalFacing.getOpposite(); + //return facing == horizontalFacing || facing == horizontalFacing.getOpposite(); + default: // Ignore rest -> TODO: Other blocks like anvils, etc... + return true; + } + } else { + if (stateSchematic.getBlock() instanceof TorchBlock && !(stateSchematic.getBlock() instanceof WallTorchBlock) && !(stateSchematic.getBlock() instanceof WallRedstoneTorchBlock)) { + return Direction.DOWN == primaryFacing; + } + return true; + } + } + + private static boolean isReplaceableFluidSource(BlockState checkState) { + return checkState.getBlock() instanceof FluidBlock && checkState.get(FluidBlock.LEVEL) == 0 || + checkState.getBlock() instanceof BubbleColumnBlock || + checkState.isOf(Blocks.SEAGRASS) || checkState.isOf(Blocks.TALL_SEAGRASS) || + checkState.getBlock() instanceof Waterloggable && checkState.get(Properties.WATERLOGGED) && BedrockBreaker.isReplaceable(checkState); + } + + static boolean isReplaceableWaterFluidSource(BlockState checkState) { + return checkState.isOf(Blocks.SEAGRASS) || checkState.isOf(Blocks.TALL_SEAGRASS) || + checkState.isOf(Blocks.WATER) && checkState.contains(FluidBlock.LEVEL) && checkState.get(FluidBlock.LEVEL) == 0 || + checkState.getBlock() instanceof BubbleColumnBlock || + checkState.getBlock() instanceof Waterloggable && checkState.contains(Properties.WATERLOGGED) && checkState.get(Properties.WATERLOGGED) && BedrockBreaker.isReplaceable(checkState); + } + + private static boolean containsWaterloggable(BlockState state) { + return state.getBlock() instanceof Waterloggable && state.get(Properties.WATERLOGGED); + } + + private static boolean requiresMoreAction(BlockState stateSchematic, BlockState stateClient) { + // Return true if current state requires more action to be taken + Block blockSchematic = stateSchematic.getBlock(); + if (blockSchematic instanceof SeaPickleBlock && stateSchematic.get(SeaPickleBlock.PICKLES) > 1) { + Block blockClient = stateClient.getBlock(); + + if (blockClient instanceof SeaPickleBlock && !Objects.equals(stateClient.get(SeaPickleBlock.PICKLES), stateSchematic.get(SeaPickleBlock.PICKLES))) { + return blockSchematic != blockClient; + } + } + if (blockSchematic instanceof SnowBlock) { + Block blockClient = stateClient.getBlock(); + + if (blockClient instanceof SnowBlock && stateClient.get(SnowBlock.LAYERS) < stateSchematic.get(SnowBlock.LAYERS)) { + return false; + } + } + if (blockSchematic instanceof SlabBlock && stateSchematic.get(SlabBlock.TYPE) == SlabType.DOUBLE) { + Block blockClient = stateClient.getBlock(); + + if (blockClient instanceof SlabBlock && stateClient.get(SlabBlock.TYPE) != SlabType.DOUBLE) { + return blockSchematic != blockClient; + } + } + if (blockSchematic instanceof ComposterBlock && stateSchematic.get(ComposterBlock.LEVEL) > 0 && stateClient.getBlock() instanceof ComposterBlock) { + return !Objects.equals(stateClient.get(ComposterBlock.LEVEL), stateSchematic.get(ComposterBlock.LEVEL)); + } + Block blockClient = stateClient.getBlock(); + if (blockClient instanceof SnowBlock && stateClient.get(SnowBlock.LAYERS) < 3 && !(stateSchematic.getBlock() instanceof SnowBlock)) { + return false; + } + if (stateClient.isOf(Blocks.DIRT) && stateSchematic.isOf(Blocks.DIRT_PATH)) { + return true; + } + // finally + return !stateClient.isAir() && !BedrockBreaker.isReplaceable(stateClient); + } + + /** + * Apply hit vectors (used to be Carpet hit vec protocol, but I think it is + * uneccessary now with orientation/states programmed in) + * + * @param pos BlockPos + * @param state BlockState + * @param side random side + * @return Vec3d + */ + public static Vec3d applyHitVec(BlockPos pos, BlockState state, Direction side) { + + double dx; + double dy; + double dz; + Block block = state.getBlock(); + + /* + * I don't know if this is needed, just doing to mimic client According to the + * MC protocol wiki, the protocol expects a 1 on a side that is clicked + */ + Vec3d clickPos = Vec3d.of(pos); + if (!(block instanceof GrindstoneBlock) && block instanceof WallMountedBlock || block instanceof TorchBlock || block instanceof WallSkullBlock + || block instanceof LadderBlock + || block instanceof TripwireHookBlock || block instanceof WallSignBlock || + block instanceof EndRodBlock || block instanceof DeadCoralFanBlock) { + if (block instanceof DeadCoralFanBlock && !(block instanceof DeadCoralWallFanBlock)) { + side = Direction.UP; + clickPos = Vec3d.ofCenter(pos.down()).add(Vec3d.of(side.getVector()).multiply(0.5)); + } else if (block instanceof TorchBlock && !(block instanceof WallTorchBlock) && !(block instanceof WallRedstoneTorchBlock)) { + side = Direction.UP; + clickPos = Vec3d.ofCenter(pos.down()).add(Vec3d.of(side.getVector()).multiply(0.5)); + } else if (side == null || state.contains(WallMountedBlock.FACE) && state.get(WallMountedBlock.FACE) != BlockFace.WALL) { + if (state.contains(WallMountedBlock.FACE) && state.get(WallMountedBlock.FACE) == BlockFace.CEILING) { + side = Direction.DOWN; + } else { + side = Direction.UP; + } + clickPos = clickPos.add(0.5, 0.5, 0.5).add(Vec3d.of(side.getVector()).multiply(0.5)); + } else { + clickPos = clickPos.add(0.5, 0.5, 0.5).add(Vec3d.of(side.getVector()).multiply(0.5)); + } + //We are here because we can't use protocol. + } + dx = clickPos.x; + dy = clickPos.y; + dz = clickPos.z; + if (block instanceof StairsBlock) { + if (state.get(StairsBlock.HALF) == BlockHalf.TOP) { + dy += 0.99; + } else { + dy += 0; + } + } else if (block instanceof SlabBlock && state.get(SlabBlock.TYPE) != SlabType.DOUBLE) { + if (state.get(SlabBlock.TYPE) == SlabType.TOP) { + dy += 0.99; + } else { + dy += 0; + } + } else if (block instanceof TrapdoorBlock) { + if (state.get(TrapdoorBlock.HALF) == BlockHalf.TOP) { + dy += 0.99; + } else { + dy += 0; + } + } + return new Vec3d(dx, dy, dz); + } + + private static boolean canBypass(MinecraftClient mc, World world, BlockPos pos) { + Direction direction = world.getBlockState(pos).get(ObserverBlock.FACING); + BlockPos posOffset = pos.offset(direction.getOpposite()); + return world.getBlockState(posOffset) == null || world.getBlockState(posOffset).isAir() || (!hasPowerRelatedState(mc.world.getBlockState(posOffset).getBlock())) && mc.world.getBlockState(posOffset).getBlock().getName() == world.getBlockState(posOffset).getBlock().getName(); + + } + + private static boolean hasGui(Block checkGui) { + return checkGui instanceof CraftingTableBlock || checkGui instanceof GrindstoneBlock || checkGui instanceof LeverBlock || checkGui instanceof TrapdoorBlock || + checkGui instanceof ButtonBlock || checkGui instanceof DoorBlock || checkGui instanceof FenceGateBlock || + checkGui instanceof BedBlock || checkGui instanceof NoteBlock || checkGui instanceof BlockWithEntity; + } + + private static boolean hasPowerRelatedState(Block block) { + return block instanceof LeavesBlock || block instanceof FluidBlock || block instanceof ObserverBlock || block instanceof PistonBlock || block instanceof PoweredRailBlock || block instanceof DetectorRailBlock || + block instanceof DispenserBlock || block instanceof AbstractRedstoneGateBlock || block instanceof LeverBlock || block instanceof TrapdoorBlock || block instanceof RedstoneTorchBlock || + block instanceof DoorBlock || block instanceof RedstoneWireBlock || block instanceof RedstoneOreBlock || block instanceof RedstoneLampBlock || block instanceof NoteBlock || block instanceof FenceGateBlock || + block instanceof ScaffoldingBlock; + } + + /* + returns if its block that can update neighbors + */ + private static boolean hasWrongStateNearby(MinecraftClient mc, World schematicWorld, BlockPos pos) { + for (Direction direction : Direction.values()) { + BlockPos checkPos = pos.offset(direction); + if (hasPowerRelatedState(schematicWorld.getBlockState(checkPos).getBlock()) && schematicWorld.getBlockState(checkPos) != mc.world.getBlockState(checkPos)) { + return true; + } + } + return false; + } + + private static String hasWrongStateNearbyReason(MinecraftClient mc, World schematicWorld, BlockPos pos) { + for (Direction direction : Direction.values()) { + BlockPos checkPos = pos.offset(direction); + if (hasPowerRelatedState(schematicWorld.getBlockState(checkPos).getBlock()) && schematicWorld.getBlockState(checkPos) != mc.world.getBlockState(checkPos)) { + return "!" + checkPos.toShortString() + " STATE " + schematicWorld.getBlockState(checkPos).toString() + " does not match with current state : " + mc.world.getBlockState(checkPos).toString() + "!"; + } + } + return null; + } + + private static BlockPos hasWrongStateNearbyPos(MinecraftClient mc, World schematicWorld, BlockPos pos) { + for (Direction direction : Direction.values()) { + BlockPos checkPos = pos.offset(direction); + if (hasPowerRelatedState(schematicWorld.getBlockState(checkPos).getBlock()) && schematicWorld.getBlockState(checkPos) != mc.world.getBlockState(checkPos)) { + return checkPos; + } + } + return null; + } + + public static Vec3d applyTorchHitVec(BlockPos pos, Vec3d hitVecIn, Direction side) { + double x = pos.getX(); + double y = pos.getY(); + double z = pos.getZ(); + + double dx = hitVecIn.getX(); + double dy = hitVecIn.getY(); + double dz = hitVecIn.getZ(); + if (side == Direction.UP) { + dy = 1; + } else if (side == Direction.DOWN) { + dy = -1; + } else if (side == Direction.EAST) { + dx = 1; + } else if (side == Direction.WEST) { + dx = -1; + } else if (side == Direction.SOUTH) { + dz = 1; + } else if (side == Direction.NORTH) { + dz = -1; + } + return new Vec3d(x + dx, y + dy, z + dz); + } + + private static void updateSignText(MinecraftClient mc, World schematicWorld, BlockPos pos) { + if (isPositionCached(pos, false)) { + return; + } + if (mc.currentScreen instanceof SignEditScreen || !schematicWorld.getBlockState(pos).isIn(BlockTags.SIGNS) || signCache.contains(pos.asLong())) { + return; + } + BlockEntity entity = schematicWorld.getBlockEntity(pos); + if (entity == null) { + return; + } + BlockEntity clientEntity = mc.world.getBlockEntity(pos); + if (clientEntity == null) { + return; + } + //#if MC>=12000 + if (entity instanceof SignBlockEntity signBlockEntity && clientEntity instanceof SignBlockEntity clientSignEntity) { + if (clientSignEntity.getText(false).getMessage(0, false).getContent() != PlainTextContent.EMPTY || + clientSignEntity.getText(false).getMessage(1, false).getContent() != PlainTextContent.EMPTY || + clientSignEntity.getText(false).getMessage(2, false).getContent() != PlainTextContent.EMPTY || + clientSignEntity.getText(false).getMessage(3, false).getContent() != PlainTextContent.EMPTY ) { + MessageHolder.sendDebugMessage("Text already exists in " + pos.toShortString()); + signCache.add(pos.asLong()); + return; + } + MessageHolder.sendDebugMessage("Tries to copy sign text in " + pos.toShortString()); + signCache.add(pos.asLong()); + mc.getNetworkHandler().sendPacket( + new UpdateSignC2SPacket( + signBlockEntity.getPos(), + true, + signBlockEntity.getText(false).getMessage(0, false).getString(), + signBlockEntity.getText(false).getMessage(1, false).getString(), + signBlockEntity.getText(false).getMessage(2, false).getString(), + signBlockEntity.getText(false).getMessage(3, false).getString() + ) + ); + } + //#elseif MC>=11900 + //$$ if (entity instanceof SignBlockEntity signBlockEntity && clientEntity instanceof SignBlockEntity clientSignEntity) { + //$$ if (clientSignEntity.getTextOnRow(0, false).getContent() != TextContent.EMPTY || clientSignEntity.getTextOnRow(1, false).getContent() != TextContent.EMPTY || + //$$ clientSignEntity.getTextOnRow(2, false).getContent() != TextContent.EMPTY || + //$$ clientSignEntity.getTextOnRow(3, false).getContent() != TextContent.EMPTY) { + //$$ MessageHolder.sendDebugMessage("Text already exists in " + pos.toShortString()); + //$$ signCache.add(pos.asLong()); + //$$ return; + //$$ } + //$$ MessageHolder.sendDebugMessage("Tries to copy sign text in " + pos.toShortString()); + //$$ signCache.add(pos.asLong()); + //$$ mc.getNetworkHandler().sendPacket(new UpdateSignC2SPacket(signBlockEntity.getPos(), signBlockEntity.getTextOnRow(0, false).getString(), signBlockEntity.getTextOnRow(1, false).getString(), signBlockEntity.getTextOnRow(2, false).getString(), signBlockEntity.getTextOnRow(3, false).getString())); + //$$ } + //#elseif MC>=11700 + //$$if (entity instanceof SignBlockEntity signBlockEntity && clientEntity instanceof SignBlockEntity clientSignEntity) { + //$$ if (clientSignEntity.getTextOnRow(0, false) != LiteralText.EMPTY || clientSignEntity.getTextOnRow(1, false) != LiteralText.EMPTY || + //$$ clientSignEntity.getTextOnRow(2, false) != LiteralText.EMPTY || + //$$ clientSignEntity.getTextOnRow(3, false) != LiteralText.EMPTY) { + //$$ MessageHolder.sendDebugMessage("Text already exists in " + pos.toShortString()); + //$$ signCache.add(pos.asLong()); + //$$ return; + //$$ } + //$$ MessageHolder.sendDebugMessage("Tries to copy sign text in " + pos.toShortString()); + //$$ signCache.add(pos.asLong()); + //$$ mc.getNetworkHandler().sendPacket(new UpdateSignC2SPacket(signBlockEntity.getPos(), signBlockEntity.getTextOnRow(0, false).getString(), signBlockEntity.getTextOnRow(1, false).getString(), signBlockEntity.getTextOnRow(2, false).getString(), signBlockEntity.getTextOnRow(3, false).getString())); + //$$} + //#else + //$$if (entity instanceof SignBlockEntity && clientEntity instanceof SignBlockEntity) { + //$$ SignBlockEntity signBlockEntity = (SignBlockEntity) entity; + //$$ SignBlockEntity clientSignEntity = (SignBlockEntity) clientEntity; + //$$ if (clientSignEntity.getTextOnRow(0) != LiteralText.EMPTY || clientSignEntity.getTextOnRow(1) != LiteralText.EMPTY || + //$$ clientSignEntity.getTextOnRow(2) != LiteralText.EMPTY || + //$$ clientSignEntity.getTextOnRow(3) != LiteralText.EMPTY) { + //$$ MessageHolder.sendDebugMessage("Text already exists in " + pos.toShortString()); + //$$ signCache.add(pos.asLong()); + //$$ return; + //$$ } + //$$ MessageHolder.sendDebugMessage("Tries to copy sign text in " + pos.toShortString()); + //$$ signCache.add(pos.asLong()); + //$$ mc.getNetworkHandler().sendPacket(new UpdateSignC2SPacket(signBlockEntity.getPos(), signBlockEntity.getTextOnRow(0).getString(), signBlockEntity.getTextOnRow(1).getString(), signBlockEntity.getTextOnRow(2).getString(), signBlockEntity.getTextOnRow(3).getString())); + //$$ } + //#endif + } + + /* + * Gets the direction necessary to build the block oriented correctly. TODO: + * Need a better way to do this. + */ + private static Boolean IsBlockSupportedCarpet(Block SchematicBlock) { + if (SchematicBlock instanceof WallMountedBlock || SchematicBlock instanceof WallSkullBlock || + SchematicBlock instanceof AbstractRailBlock || SchematicBlock instanceof TorchBlock || SchematicBlock instanceof DeadCoralFanBlock) { + return false; + } + //#if MC>=12000 + return true; + //#else + //$$ return ADVANCED_ACCURATE_BLOCK_PLACEMENT.getBooleanValue() || SchematicBlock instanceof GlazedTerracottaBlock || SchematicBlock instanceof ObserverBlock || SchematicBlock instanceof RepeaterBlock || SchematicBlock instanceof TrapdoorBlock || + //$$ SchematicBlock instanceof ComparatorBlock || SchematicBlock instanceof DispenserBlock || SchematicBlock instanceof PistonBlock || SchematicBlock instanceof StairsBlock; + //#endif + } //Current carpet extra does not handle other facingBlocks, gnembon please update it + + static Direction applyPlacementFacing(BlockState stateSchematic, Direction side, BlockState stateClient) { + Block blockSchematic = stateSchematic.getBlock(); + Block blockClient = stateClient.getBlock(); + + if (blockSchematic instanceof SlabBlock) { + if (stateSchematic.get(SlabBlock.TYPE) == SlabType.DOUBLE && blockClient instanceof SlabBlock + && stateClient.get(SlabBlock.TYPE) != SlabType.DOUBLE) { + if (stateClient.get(SlabBlock.TYPE) == SlabType.TOP) { + return Direction.DOWN; + } else { + return Direction.UP; + } + } + // Single slab + else { + return Direction.NORTH; + } + } else if (/*blockSchematic instanceof LogBlock ||*/ blockSchematic instanceof PillarBlock) { + Direction.Axis axis = stateSchematic.get(PillarBlock.AXIS); + // Logs and pillars only have 3 directions that are important + if (axis == Direction.Axis.X) { + return Direction.WEST; + } else if (axis == Direction.Axis.Y) { + return Direction.DOWN; + } else if (axis == Direction.Axis.Z) { + return Direction.NORTH; + } + + } else if (blockSchematic instanceof WallSignBlock) { + return stateSchematic.get(WallSignBlock.FACING); + } else if (blockSchematic instanceof WallSkullBlock) { + return stateSchematic.get(WallSignBlock.FACING); + } else if (blockSchematic instanceof SignBlock) { + return Direction.UP; + } else if (blockSchematic instanceof WallMountedBlock) { + BlockFace location = stateSchematic.get(WallMountedBlock.FACE); + if (location == BlockFace.FLOOR) { + return Direction.UP; + } else if (location == BlockFace.CEILING) { + return Direction.DOWN; + } else { + return stateSchematic.get(WallMountedBlock.FACING); + } + } else if (blockSchematic instanceof DeadCoralWallFanBlock) { + return stateSchematic.get(DeadCoralWallFanBlock.FACING); + } else if (blockSchematic instanceof DeadCoralFanBlock) { + return Direction.UP; + } else if (blockSchematic instanceof HopperBlock) { + return stateSchematic.get(HopperBlock.FACING).getOpposite(); + //#if MC>=11700 + } else if (blockSchematic instanceof LightningRodBlock) { + return stateSchematic.get(LightningRodBlock.FACING); + //#endif + } else if (stateSchematic.isIn(BlockTags.SHULKER_BOXES)) { + return stateSchematic.get(ShulkerBoxBlock.FACING); + } else if (blockSchematic instanceof TorchBlock) { + if (blockSchematic instanceof WallTorchBlock || blockSchematic instanceof WallRedstoneTorchBlock) { + return stateSchematic.get(WallTorchBlock.FACING); + } else { + return Direction.UP; + } + } else if (blockSchematic instanceof LadderBlock) { + return stateSchematic.get(LadderBlock.FACING); + } else if (blockSchematic instanceof TrapdoorBlock) { + if (LitematicaMixinMod.PRINTER_ACCURATE_BLOCK_PLACEMENT.getBooleanValue()) { + return Direction.UP; //Placement State fixing first + } + return stateSchematic.get(TrapdoorBlock.FACING); + } else if (blockSchematic instanceof TripwireHookBlock) { + return stateSchematic.get(TripwireHookBlock.FACING); + } else if (blockSchematic instanceof EndRodBlock) { + return stateSchematic.get(EndRodBlock.FACING); + } else if (blockSchematic instanceof AnvilBlock) { + if (LitematicaMixinMod.ADVANCED_ACCURATE_BLOCK_PLACEMENT.getBooleanValue() || LitematicaMixinMod.PRINTER_ACCURATE_BLOCK_PLACEMENT.getBooleanValue() && IsBlockSupportedCarpet(blockSchematic)) { + return stateSchematic.get(AnvilBlock.FACING); + } + return stateSchematic.get(AnvilBlock.FACING).rotateYCounterclockwise(); + } else if (blockSchematic instanceof AbstractRailBlock) { + return convertRailShapetoFace(stateSchematic); + } + + // TODO: Add more for other blocks + return side; + } + + public static Direction convertRailShapetoFace(BlockState state) { + String RailShape; + if (state.getBlock() instanceof RailBlock) { + RailShape = state.get(RailBlock.SHAPE).toString(); + } else { + RailShape = state.get(PoweredRailBlock.SHAPE).toString(); + } + if (RailShape.contains("east") || RailShape.contains("west")) { + return Direction.EAST; + } else { + return Direction.NORTH; + } + } + + public static boolean isPositionCached(BlockPos pos, boolean useClicked) { + long currentTime = System.nanoTime(); + boolean cached = false; + //#if MC>=11900 + for (Pair keys : List.copyOf(positionCache.keySet())) { + //#else + //$$ ArrayList> keySet = new ArrayList<>(positionCache.keySet()); + //$$ for (Pair keys : keySet) { + //#endif + PositionCache val = positionCache.get(keys); + boolean expired = val.hasExpired(currentTime); + + if (expired) { + positionCache.remove(keys); + } else if (val.getPos().equals(pos)) { + // Item placement and "using"/"clicking" (changing delay for repeaters) are + // diffferent + if (!useClicked || val.hasClicked) { + cached = true; + } + // Keep checking and removing old entries if there are a fair amount + if (positionCache.size() < 16) { + break; + } + } + } + return cached; + } + + public static void cacheEasyPlacePosition(BlockPos pos, boolean useClicked) { + PositionCache item = new PositionCache(pos, System.nanoTime(), useClicked ? LitematicaMixinMod.EASY_PLACE_CACHE_TIME.getIntegerValue() * 1000000L : 2800000000L); + // TODO: Create a separate cache for clickable items, as this just makes + // duplicates + if (useClicked) { + item.hasClicked = true; + } + Pair entry = new Pair<>(pos.asLong(), useClicked); + if (positionCache.containsKey(entry)) { + PositionCache value = positionCache.get(entry); + if (item.timeout > value.timeout) { + positionCache.put(entry, item); + } + } else { + positionCache.put(entry, item); + } + } + + public static void cacheEasyPlacePosition(BlockPos pos, boolean useClicked, int miliseconds) { + PositionCache item = new PositionCache(pos, System.nanoTime(), miliseconds * 1000000L); + // TODO: Create a separate cache for clickable items, as this just makes + // duplicates + if (useClicked) { + item.hasClicked = true; + } + Pair entry = new Pair<>(pos.asLong(), useClicked); + if (positionCache.containsKey(entry)) { + PositionCache value = positionCache.get(entry); + if (item.timeout > value.timeout) { + positionCache.put(entry, item); + } + } else { + positionCache.put(entry, item); + } + } + + public static Vec3d applyCarpetProtocolHitVec(BlockPos pos, BlockState state) { + //#if MC>=11700 + if (Configs.Generic.EASY_PLACE_PROTOCOL.getOptionListValue() == EasyPlaceProtocol.V3) { + //#else + //$$ if (Configs.Generic.EASY_PLACE_PROTOCOL_V3.getBooleanValue()) { + //#endif + return WorldUtils.applyPlacementProtocolV3(pos, state, new Vec3d(pos.getX(), pos.getY(), pos.getZ())); + } + Vec3d hitVec = new Vec3d(pos.getX(), pos.getY(), pos.getZ()); + //#if MC >= 12000 + hitVec = WorldUtils.applyCarpetProtocolHitVec(pos, state, hitVec); + return hitVec; + //#else + //$$ double code = 0; + //$$ double y = pos.getY(); + //$$ double z = pos.getZ(); + //$$ Block block = state.getBlock(); + //$$ Direction facing = fi.dy.masa.malilib.util.BlockUtils.getFirstPropertyFacingValue(state); + //$$ int railEnumCode = getRailShapeOrder(state); + //$$ final int propertyIncrement = 16; + //$$ if (facing == null && railEnumCode == 32 && !(block instanceof SlabBlock)) { + //$$ return new Vec3d(pos.getX(), y, z); + //$$ } + //$$ if (facing != null) { + //$$ code = facing.getId(); + //$$ } else if (railEnumCode != 32) { + //$$ code = railEnumCode; + //$$ } + //$$ if (block instanceof RepeaterBlock) { + //$$ code += ((state.get(RepeaterBlock.DELAY))) * (propertyIncrement); + //$$ } else if (block instanceof TrapdoorBlock && state.get(TrapdoorBlock.HALF) == BlockHalf.TOP) { + //$$ code += propertyIncrement; + //$$ } else if (block instanceof ComparatorBlock && state.get(ComparatorBlock.MODE) == ComparatorMode.SUBTRACT) { + //$$ code += propertyIncrement; + //$$ } else if (block instanceof StairsBlock && state.get(StairsBlock.HALF) == BlockHalf.TOP) { + //$$ code += propertyIncrement; + //$$ } else if (block instanceof SlabBlock && state.get(SlabBlock.TYPE) != SlabType.DOUBLE) { + //$$ if (state.get(SlabBlock.TYPE) == SlabType.TOP) //side should not be down + //$$ { + //$$ y += 0.99; + //$$ //code += propertyIncrement; //slab type by protocol soon? + //$$ } + //$$ } + //$$ if (code >= 0) { + //$$ return new Vec3d(code * 2 + 2 + pos.getX(), y, z); + //$$ } + //$$ hitVec = new Vec3d(pos.getX(), y, z); + //$$ return hitVec; + //#endif + } + + public static Integer getRailShapeOrder(BlockState state) { + Block stateBlock = state.getBlock(); + if (stateBlock instanceof AbstractRailBlock) { + if (stateBlock instanceof RailBlock) { + return state.get(RailBlock.SHAPE).ordinal(); + } else if (stateBlock instanceof DetectorRailBlock) { + return state.get(DetectorRailBlock.SHAPE).ordinal(); + } else { + return state.get(PoweredRailBlock.SHAPE).ordinal(); + } + } else { + return 32; + } + } + + + public static class PositionCache { + private final BlockPos pos; + private final long timeout; + public boolean hasClicked = false; + + private PositionCache(BlockPos pos, long time, long timeout) { + this.pos = pos; + this.timeout = time + timeout; + } + + public BlockPos getPos() { + return this.pos; + } + + public boolean hasExpired(long currentTime) { + return currentTime > this.timeout; + } + } +} diff --git a/versions/mapping-1.20.5-1.20.4.txt b/versions/mapping-1.20.6-1.20.4.txt similarity index 100% rename from versions/mapping-1.20.5-1.20.4.txt rename to versions/mapping-1.20.6-1.20.4.txt diff --git a/versions/mapping-1.21.0-1.20.6.txt b/versions/mapping-1.21.0-1.20.6.txt new file mode 100644 index 00000000..e69de29b