diff --git a/gradle.properties b/gradle.properties index 878f11930..40e06a096 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,7 +8,7 @@ org.gradle.jvmargs=-Xmx1G loader_version=0.12.10 # Mod Properties - mod_version = 2.0.0 + mod_version = 2.0 maven_group = net.fabricmc archives_base_name = litematica-printer diff --git a/src/main/java/me/aleksilassila/litematica/printer/LitematicaMixinMod.java b/src/main/java/me/aleksilassila/litematica/printer/LitematicaMixinMod.java index 97b4f05b1..e26f79544 100644 --- a/src/main/java/me/aleksilassila/litematica/printer/LitematicaMixinMod.java +++ b/src/main/java/me/aleksilassila/litematica/printer/LitematicaMixinMod.java @@ -18,120 +18,32 @@ public class LitematicaMixinMod implements ModInitializer { public static final ConfigInteger PRINT_INTERVAL = new ConfigInteger( "printInterval", 4, 2, 20, "Print interval in game ticks. Lower values mean faster printing speed.\nIf the printer creates \"ghost blocks\", raise this value."); public static final ConfigInteger PRINTING_RANGE = new ConfigInteger("printingRange", 2, 1, 6, "Printing block place range\nLower values are recommended for servers."); // public static final ConfigBoolean PRINT_WATER = new ConfigBoolean("printWater", false, "Whether or not the printer should place water\n source blocks or make blocks waterlogged."); - public static final ConfigBoolean PRINT_IN_AIR = new ConfigBoolean("printInAir", false, "Whether or not the printer should place blocks without anything to build on.\nBe aware that some anti-cheat plugins might notice this."); +// public static final ConfigBoolean PRINT_IN_AIR = new ConfigBoolean("printInAir", false, "Whether or not the printer should place blocks without anything to build on.\nBe aware that some anti-cheat plugins might notice this."); public static final ConfigBoolean PRINT_MODE = new ConfigBoolean("printingMode", false, "Autobuild / print loaded selection.\nBe aware that some servers and anticheat plugins do not allow printing."); public static final ConfigBoolean REPLACE_FLUIDS = new ConfigBoolean("replaceFluids", false, "Whether or not fluid source blocks should be replaced by the printer."); - public static final ImmutableList betterConfigList = ImmutableList.of( - Configs.Generic.AREAS_PER_WORLD, - //BETTER_RENDER_ORDER, - Configs.Generic.CHANGE_SELECTED_CORNER, - Configs.Generic.EASY_PLACE_MODE, - Configs.Generic.EASY_PLACE_HOLD_ENABLED, - Configs.Generic.EXECUTE_REQUIRE_TOOL, - Configs.Generic.FIX_RAIL_ROTATION, - Configs.Generic.LOAD_ENTIRE_SCHEMATICS, - Configs.Generic.PASTE_IGNORE_INVENTORY, - Configs.Generic.PICK_BLOCK_ENABLED, - Configs.Generic.PLACEMENT_RESTRICTION, - Configs.Generic.RENDER_MATERIALS_IN_GUI, - Configs.Generic.RENDER_THREAD_NO_TIMEOUT, - Configs.Generic.TOOL_ITEM_ENABLED, + public static ImmutableList getConfigList() { + List list = new java.util.ArrayList<>(List.copyOf(Configs.Generic.OPTIONS)); + list.add(PRINT_INTERVAL); + list.add(PRINTING_RANGE); +// list.add(PRINT_IN_AIR); + list.add(PRINT_MODE); + list.add(REPLACE_FLUIDS); - Configs.Generic.PASTE_REPLACE_BEHAVIOR, - Configs.Generic.SELECTION_CORNERS_MODE, - - Configs.Generic.PASTE_COMMAND_INTERVAL, - Configs.Generic.PASTE_COMMAND_LIMIT, - Configs.Generic.PASTE_COMMAND_SETBLOCK, - Configs.Generic.PICK_BLOCKABLE_SLOTS, - Configs.Generic.TOOL_ITEM, - - PRINT_INTERVAL, - PRINTING_RANGE, -// PRINT_WATER, - PRINT_IN_AIR, - PRINT_MODE, - REPLACE_FLUIDS - ); + return ImmutableList.copyOf(list); + } // Hotkeys - public static final ConfigHotkey TOGGLE_PRINTING_MODE = new ConfigHotkey("togglePrintingMode", "M,O", "Allows quickly toggling on/off Printing mode"); - - public static final List betterHotkeyList = ImmutableList.of( - Hotkeys.ADD_SELECTION_BOX, - Hotkeys.CLONE_SELECTION, - Hotkeys.DELETE_SELECTION_BOX, - Hotkeys.EASY_PLACE_ACTIVATION, - Hotkeys.EASY_PLACE_TOGGLE, - Hotkeys.EXECUTE_OPERATION, - Hotkeys.INVERT_GHOST_BLOCK_RENDER_STATE, - Hotkeys.INVERT_OVERLAY_RENDER_STATE, - Hotkeys.LAYER_MODE_NEXT, - Hotkeys.LAYER_MODE_PREVIOUS, - Hotkeys.LAYER_NEXT, - Hotkeys.LAYER_PREVIOUS, - Hotkeys.LAYER_SET_HERE, - Hotkeys.NUDGE_SELECTION_NEGATIVE, - Hotkeys.NUDGE_SELECTION_POSITIVE, - Hotkeys.MOVE_ENTIRE_SELECTION, - Hotkeys.OPEN_GUI_AREA_SETTINGS, - Hotkeys.OPEN_GUI_LOADED_SCHEMATICS, - Hotkeys.OPEN_GUI_MAIN_MENU, - Hotkeys.OPEN_GUI_MATERIAL_LIST, - Hotkeys.OPEN_GUI_PLACEMENT_SETTINGS, - Hotkeys.OPEN_GUI_SCHEMATIC_PLACEMENTS, - Hotkeys.OPEN_GUI_SCHEMATIC_PROJECTS, - Hotkeys.OPEN_GUI_SCHEMATIC_VERIFIER, - Hotkeys.OPEN_GUI_SELECTION_MANAGER, - Hotkeys.OPEN_GUI_SETTINGS, - Hotkeys.OPERATION_MODE_CHANGE_MODIFIER, - Hotkeys.PICK_BLOCK_FIRST, - Hotkeys.PICK_BLOCK_LAST, - Hotkeys.PICK_BLOCK_TOGGLE, - Hotkeys.RENDER_INFO_OVERLAY, - Hotkeys.RENDER_OVERLAY_THROUGH_BLOCKS, - Hotkeys.RERENDER_SCHEMATIC, - Hotkeys.SAVE_AREA_AS_IN_MEMORY_SCHEMATIC, - Hotkeys.SAVE_AREA_AS_SCHEMATIC_TO_FILE, - Hotkeys.SCHEMATIC_REBUILD_BREAK_ALL, - Hotkeys.SCHEMATIC_REBUILD_BREAK_ALL_EXCEPT, - Hotkeys.SCHEMATIC_REBUILD_BREAK_DIRECTION, - Hotkeys.SCHEMATIC_REBUILD_REPLACE_ALL, - Hotkeys.SCHEMATIC_REBUILD_REPLACE_DIRECTION, - Hotkeys.SCHEMATIC_VERSION_CYCLE_MODIFIER, - Hotkeys.SCHEMATIC_VERSION_CYCLE_NEXT, - Hotkeys.SCHEMATIC_VERSION_CYCLE_PREVIOUS, - Hotkeys.SELECTION_GRAB_MODIFIER, - Hotkeys.SELECTION_GROW_HOTKEY, - Hotkeys.SELECTION_GROW_MODIFIER, - Hotkeys.SELECTION_NUDGE_MODIFIER, - Hotkeys.SELECTION_MODE_CYCLE, - Hotkeys.SELECTION_SHRINK_HOTKEY, - Hotkeys.SET_AREA_ORIGIN, - Hotkeys.SET_SELECTION_BOX_POSITION_1, - Hotkeys.SET_SELECTION_BOX_POSITION_2, - Hotkeys.TOGGLE_ALL_RENDERING, - Hotkeys.TOGGLE_AREA_SELECTION_RENDERING, - Hotkeys.TOGGLE_INFO_OVERLAY_RENDERING, - Hotkeys.TOGGLE_OVERLAY_RENDERING, - Hotkeys.TOGGLE_OVERLAY_OUTLINE_RENDERING, - Hotkeys.TOGGLE_OVERLAY_SIDE_RENDERING, - Hotkeys.TOGGLE_PLACEMENT_BOXES_RENDERING, - Hotkeys.TOGGLE_PLACEMENT_RESTRICTION, - Hotkeys.TOGGLE_SCHEMATIC_BLOCK_RENDERING, - Hotkeys.TOGGLE_SCHEMATIC_RENDERING, - Hotkeys.TOGGLE_TRANSLUCENT_RENDERING, - Hotkeys.TOGGLE_VERIFIER_OVERLAY_RENDERING, - Hotkeys.TOOL_ENABLED_TOGGLE, - Hotkeys.TOOL_PLACE_CORNER_1, - Hotkeys.TOOL_PLACE_CORNER_2, - Hotkeys.TOOL_SELECT_ELEMENTS, - Hotkeys.TOOL_SELECT_MODIFIER_BLOCK_1, - Hotkeys.TOOL_SELECT_MODIFIER_BLOCK_2, - Hotkeys.UNLOAD_CURRENT_SCHEMATIC, - TOGGLE_PRINTING_MODE - ); + public static final ConfigHotkey PRINT = new ConfigHotkey("print", "V", "Prints while pressed"); + public static final ConfigHotkey TOGGLE_PRINTING_MODE = new ConfigHotkey("togglePrintingMode", "CAPS_LOCK", "Allows quickly toggling on/off Printing mode"); + + public static List getHotkeyList() { + List list = new java.util.ArrayList<>(List.copyOf(Hotkeys.HOTKEY_LIST)); + list.add(PRINT); + list.add(TOGGLE_PRINTING_MODE); + + return ImmutableList.copyOf(list); + } @Override public void onInitialize() { diff --git a/src/main/java/me/aleksilassila/litematica/printer/mixin/ClientPlayNetworkHandlerMixin.java b/src/main/java/me/aleksilassila/litematica/printer/mixin/ClientPlayNetworkHandlerMixin.java index 7634e6978..ab2e76810 100644 --- a/src/main/java/me/aleksilassila/litematica/printer/mixin/ClientPlayNetworkHandlerMixin.java +++ b/src/main/java/me/aleksilassila/litematica/printer/mixin/ClientPlayNetworkHandlerMixin.java @@ -7,10 +7,8 @@ import net.minecraft.network.ClientConnection; import net.minecraft.network.Packet; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; 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; @Mixin(ClientPlayNetworkHandler.class) public class ClientPlayNetworkHandlerMixin { @@ -20,16 +18,16 @@ public class ClientPlayNetworkHandlerMixin { @Shadow private MinecraftClient client; - @Inject(method = "sendPacket", at = @At("HEAD")) - public void sendPacket(Packet packet, CallbackInfo ci) { + @Overwrite + public void sendPacket(Packet packet) { if (Implementation.isLookPacket(packet) && Printer.shouldBlockLookPackets()) { Packet positionOnlyPacket = Implementation.getMoveOnlyPacket(client.player, packet); if (positionOnlyPacket != null) { this.connection.send(positionOnlyPacket); } - - return; + } else { + this.connection.send(packet); } } } diff --git a/src/main/java/me/aleksilassila/litematica/printer/mixin/ConfigsMixin.java b/src/main/java/me/aleksilassila/litematica/printer/mixin/ConfigsMixin.java index 8ce88b71c..131e9eed2 100644 --- a/src/main/java/me/aleksilassila/litematica/printer/mixin/ConfigsMixin.java +++ b/src/main/java/me/aleksilassila/litematica/printer/mixin/ConfigsMixin.java @@ -13,78 +13,23 @@ @Mixin(value = Configs.class, remap = false) public class ConfigsMixin { - @Redirect(method = "loadFromFile", at = @At(value = "FIELD", target = "Lfi/dy/masa/litematica/config/Configs$Generic;OPTIONS:Lcom/google/common/collect/ImmutableList;")) private static ImmutableList moreOptions() { - return LitematicaMixinMod.betterConfigList; + return LitematicaMixinMod.getConfigList(); } @Redirect(method = "saveToFile", at = @At(value = "FIELD", target = "Lfi/dy/masa/litematica/config/Configs$Generic;OPTIONS:Lcom/google/common/collect/ImmutableList;")) private static ImmutableList moreeOptions() { - return LitematicaMixinMod.betterConfigList; + return LitematicaMixinMod.getConfigList(); } @Redirect(method = "loadFromFile", at = @At(value = "FIELD", target = "Lfi/dy/masa/litematica/config/Hotkeys;HOTKEY_LIST:Ljava/util/List;")) private static List moreHotkeys() { - return LitematicaMixinMod.betterHotkeyList; + return LitematicaMixinMod.getHotkeyList(); } @Redirect(method = "saveToFile", at = @At(value = "FIELD", target = "Lfi/dy/masa/litematica/config/Hotkeys;HOTKEY_LIST:Ljava/util/List;")) private static List moreeHotkeys() { - return LitematicaMixinMod.betterHotkeyList; + return LitematicaMixinMod.getHotkeyList(); } - - - - /* - * I couldn't get the redirect mixin working to redirect two fields in one method. - * If you have any idea... - */ - /*@Shadow - private static String CONFIG_FILE_NAME; - - @Overwrite - public static void loadFromFile() - { - File configFile = new File(FileUtils.getConfigDirectory(), CONFIG_FILE_NAME); - - if (configFile.exists() && configFile.isFile() && configFile.canRead()) - { - JsonElement element = JsonUtils.parseJsonFile(configFile); - - if (element != null && element.isJsonObject()) - { - JsonObject root = element.getAsJsonObject(); - - ConfigUtils.readConfigBase(root, "Colors", Colors.OPTIONS); - ConfigUtils.readConfigBase(root, "Generic", LitematicaMixinMod.betterConfigList); - ConfigUtils.readConfigBase(root, "Hotkeys", LitematicaMixinMod.betterHotkeyList); - ConfigUtils.readConfigBase(root, "InfoOverlays", InfoOverlays.OPTIONS); - ConfigUtils.readConfigBase(root, "Visuals", Visuals.OPTIONS); - } - } - - DataManager.setToolItem(Generic.TOOL_ITEM.getStringValue()); - InventoryUtils.setPickBlockableSlots(Generic.PICK_BLOCKABLE_SLOTS.getStringValue()); - } - - @Overwrite - public static void saveToFile() - { - File dir = FileUtils.getConfigDirectory(); - - if ((dir.exists() && dir.isDirectory()) || dir.mkdirs()) - { - JsonObject root = new JsonObject(); - - ConfigUtils.writeConfigBase(root, "Colors", Colors.OPTIONS); - ConfigUtils.writeConfigBase(root, "Generic", LitematicaMixinMod.betterConfigList); - ConfigUtils.writeConfigBase(root, "Hotkeys", LitematicaMixinMod.betterHotkeyList); - ConfigUtils.writeConfigBase(root, "InfoOverlays", InfoOverlays.OPTIONS); - ConfigUtils.writeConfigBase(root, "Visuals", Visuals.OPTIONS); - - JsonUtils.writeJsonToFile(root, new File(dir, CONFIG_FILE_NAME)); - } - }*/ - } diff --git a/src/main/java/me/aleksilassila/litematica/printer/mixin/GuiConfigsMixin.java b/src/main/java/me/aleksilassila/litematica/printer/mixin/GuiConfigsMixin.java index 698c628fd..eab1c975f 100644 --- a/src/main/java/me/aleksilassila/litematica/printer/mixin/GuiConfigsMixin.java +++ b/src/main/java/me/aleksilassila/litematica/printer/mixin/GuiConfigsMixin.java @@ -51,11 +51,11 @@ else if (tab == ConfigGuiTab.HOTKEYS) @Redirect(method = "getConfigs", at = @At(value = "FIELD", target = "Lfi/dy/masa/litematica/config/Configs$Generic;OPTIONS:Lcom/google/common/collect/ImmutableList;")) private ImmutableList moreOptions() { - return LitematicaMixinMod.betterConfigList; + return LitematicaMixinMod.getConfigList(); } @Redirect(method = "getConfigs", at = @At(value = "FIELD", target = "Lfi/dy/masa/litematica/config/Hotkeys;HOTKEY_LIST:Ljava/util/List;")) private List moreHotkeys() { - return LitematicaMixinMod.betterHotkeyList; + return LitematicaMixinMod.getHotkeyList(); } } diff --git a/src/main/java/me/aleksilassila/litematica/printer/mixin/InputHandlerMixin.java b/src/main/java/me/aleksilassila/litematica/printer/mixin/InputHandlerMixin.java index b7fdf8a86..6ee4c3e75 100644 --- a/src/main/java/me/aleksilassila/litematica/printer/mixin/InputHandlerMixin.java +++ b/src/main/java/me/aleksilassila/litematica/printer/mixin/InputHandlerMixin.java @@ -14,12 +14,12 @@ public class InputHandlerMixin { @Redirect(method = "addHotkeys", at = @At(value = "FIELD", target = "Lfi/dy/masa/litematica/config/Hotkeys;HOTKEY_LIST:Ljava/util/List;")) private List moreHotkeys() { - return LitematicaMixinMod.betterHotkeyList; + return LitematicaMixinMod.getHotkeyList(); } @Redirect(method = "addKeysToMap", at = @At(value = "FIELD", target = "Lfi/dy/masa/litematica/config/Hotkeys;HOTKEY_LIST:Ljava/util/List;")) private List moreeHotkeys() { - return LitematicaMixinMod.betterHotkeyList; + return LitematicaMixinMod.getHotkeyList(); } } diff --git a/src/main/java/me/aleksilassila/litematica/printer/mixin/MixinClientPlayerEntity.java b/src/main/java/me/aleksilassila/litematica/printer/mixin/MixinClientPlayerEntity.java index 79207269b..3a6915f78 100644 --- a/src/main/java/me/aleksilassila/litematica/printer/mixin/MixinClientPlayerEntity.java +++ b/src/main/java/me/aleksilassila/litematica/printer/mixin/MixinClientPlayerEntity.java @@ -16,7 +16,6 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(ClientPlayerEntity.class) public class MixinClientPlayerEntity extends AbstractClientPlayerEntity { @@ -31,13 +30,6 @@ public MixinClientPlayerEntity(ClientWorld world, GameProfile profile) { protected Printer printer; - @Inject(at = @At("RETURN"), method = "isCamera", cancellable = true) - protected void isCamera(CallbackInfoReturnable cir) { - if (printer != null && printer.lockCamera) { - cir.setReturnValue(false); // Fix for placing correctly pistons for example - } - } - @Inject(at = @At("HEAD"), method = "tick") public void tick(CallbackInfo ci) { if (!didCheckForUpdates) { @@ -54,7 +46,9 @@ public void tick(CallbackInfo ci) { return; } - if (SchematicWorldHandler.getSchematicWorld() == null || !LitematicaMixinMod.PRINT_MODE.getBooleanValue()) return; + if (SchematicWorldHandler.getSchematicWorld() == null || + !(LitematicaMixinMod.PRINT_MODE.getBooleanValue() || LitematicaMixinMod.PRINT.getKeybind().isPressed())) + return; printer.onTick(); } diff --git a/src/main/java/me/aleksilassila/litematica/printer/mixin/MixinClientPlayerInteractionManager.java b/src/main/java/me/aleksilassila/litematica/printer/mixin/MixinClientPlayerInteractionManager.java index 01c41f7e8..71f00782b 100644 --- a/src/main/java/me/aleksilassila/litematica/printer/mixin/MixinClientPlayerInteractionManager.java +++ b/src/main/java/me/aleksilassila/litematica/printer/mixin/MixinClientPlayerInteractionManager.java @@ -15,6 +15,9 @@ import net.minecraft.world.World; 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.CallbackInfoReturnable; @Mixin(ClientPlayerInteractionManager.class) public abstract class MixinClientPlayerInteractionManager implements IClientPlayerInteractionManager { @@ -27,6 +30,7 @@ public void rightClickBlock(BlockPos pos, Direction side, Vec3d hitVec) interactBlock(client.player, client.world, Hand.MAIN_HAND, new BlockHitResult(hitVec, side, pos, false)); interactItem(client.player, client.world, Hand.MAIN_HAND); + System.out.println("Printer interactBlock: pos: (" + pos.toShortString() + "), side: " + side.getName() + ", vector: " + hitVec.toString()); } @Shadow @@ -37,4 +41,9 @@ public abstract ActionResult interactBlock( @Shadow public abstract ActionResult interactItem(PlayerEntity playerEntity_1, World world_1, Hand hand_1); + + @Inject(at = @At("HEAD"), method = "interactBlock") + public void interactBlock(ClientPlayerEntity player, ClientWorld world, Hand hand, BlockHitResult hitResult, CallbackInfoReturnable cir) { + System.out.println("Player interactBlock: pos: (" + hitResult.getBlockPos().toShortString() + "), side: " + hitResult.getSide().getName() + ", vector: " + hitResult.getPos().toString()); + } } diff --git a/src/main/java/me/aleksilassila/litematica/printer/printer/ClickGuide.java b/src/main/java/me/aleksilassila/litematica/printer/printer/ClickGuide.java new file mode 100644 index 000000000..0441d56a3 --- /dev/null +++ b/src/main/java/me/aleksilassila/litematica/printer/printer/ClickGuide.java @@ -0,0 +1,104 @@ +package me.aleksilassila.litematica.printer.printer; + +import net.minecraft.block.*; +import net.minecraft.item.Item; +import net.minecraft.item.Items; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; + +public enum ClickGuide { + SNOW(SnowBlock.class), + CANDLES(AbstractCandleBlock.class), + LEVER(LeverBlock.class), + REPEATER(RepeaterBlock.class), + COMPARATOR(ComparatorBlock.class), + TRAPDOOR(TrapdoorBlock.class), + DOOR(DoorBlock.class), + PICKLES(SeaPickleBlock.class), + FENCE(FenceGateBlock.class), + DEFAULT; + + private final Class[] matchClasses; + + ClickGuide(Class ... classes) { + matchClasses = classes; + } + + private static ClickGuide getGuide(BlockState requiredState, BlockState currentState) { + for (ClickGuide guide : ClickGuide.values()) { + for (Class clazz : guide.matchClasses) { + if (clazz.isInstance(requiredState.getBlock()) && + clazz.isInstance(currentState.getBlock())) { + return guide; + } + } + } + + return DEFAULT; + } + + public static Click shouldClickBlock(BlockState requiredState, BlockState currentState) { + switch(getGuide(requiredState, currentState)) { + case SNOW -> { + if (currentState.get(SnowBlock.LAYERS) < requiredState.get(SnowBlock.LAYERS)) { + return new Click(true, Items.SNOW); + } + } + case DOOR -> { + if (requiredState.get(DoorBlock.OPEN) != currentState.get(DoorBlock.OPEN)) + return new Click(true); + } + case LEVER -> { + System.out.println("Caught lever, required: " + requiredState.get(LeverBlock.POWERED) + ", current: " + currentState.get(LeverBlock.POWERED)); + if (requiredState.get(LeverBlock.POWERED) != currentState.get(LeverBlock.POWERED)) + return new Click(true); + } + case CANDLES -> { + if (currentState.get(CandleBlock.CANDLES) < requiredState.get(CandleBlock.CANDLES)) + return new Click(true, requiredState.getBlock().asItem()); + } + case PICKLES -> { + if (currentState.get(SeaPickleBlock.PICKLES) < requiredState.get(SeaPickleBlock.PICKLES)) + return new Click(true, Items.SEA_PICKLE); + } + case REPEATER -> { + if (!Objects.equals(requiredState.get(RepeaterBlock.DELAY), currentState.get(RepeaterBlock.DELAY))) + return new Click(true); + } + case COMPARATOR -> { + if (requiredState.get(ComparatorBlock.MODE) != currentState.get(ComparatorBlock.MODE)) + return new Click(true); + } + case TRAPDOOR -> { + if (requiredState.get(TrapdoorBlock.OPEN) != currentState.get(TrapdoorBlock.OPEN)) + return new Click(true); + } + case FENCE -> { + if (requiredState.get(FenceGateBlock.OPEN) != currentState.get(FenceGateBlock.OPEN)) + return new Click(true); + } + } + + return new Click(); + } + + public static class Click { + public final boolean click; + @Nullable + public final Item item; + + public Click(boolean click, @Nullable Item item) { + this.click = click; + this.item = item; + } + + public Click(boolean click) { + this(click, null); + } + + public Click() { + this(false, null); + } + } +} diff --git a/src/main/java/me/aleksilassila/litematica/printer/printer/PlacementGuide.java b/src/main/java/me/aleksilassila/litematica/printer/printer/PlacementGuide.java new file mode 100644 index 000000000..f9f11eba1 --- /dev/null +++ b/src/main/java/me/aleksilassila/litematica/printer/printer/PlacementGuide.java @@ -0,0 +1,244 @@ +package me.aleksilassila.litematica.printer.printer; + +import net.minecraft.block.*; +import net.minecraft.block.enums.*; +import net.minecraft.state.property.DirectionProperty; +import net.minecraft.state.property.Property; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3d; +import org.jetbrains.annotations.Nullable; + +public enum PlacementGuide { + ROD(RodBlock.class), + WALLTORCH(WallTorchBlock.class), + TORCH(TorchBlock.class), + SLAB(SlabBlock.class), + STAIR(StairsBlock.class), + TRAPDOOR(TrapdoorBlock.class), + PILLAR(PillarBlock.class), + ANVIL(AnvilBlock.class), + HOPPER(HopperBlock.class), + WALLMOUNTED(LeverBlock.class, AbstractButtonBlock.class), +// GRINDSTONE(GrindstoneBlock.class), + GATE(FenceGateBlock.class), + CAMPFIRE(CampfireBlock.class), + SHULKER(ShulkerBoxBlock.class), + BED(BedBlock.class), + BELL(BellBlock.class), + AMETHYST(AmethystClusterBlock.class), + DOOR(DoorBlock.class), + COCOA(CocoaBlock.class), + OBSERVER(ObserverBlock.class), + WALLSKULL(WallSkullBlock.class), + SKIP(SkullBlock.class, GrindstoneBlock.class, SignBlock.class, AbstractLichenBlock.class, VineBlock.class), + DEFAULT; + + private final Class[] matchClasses; + + PlacementGuide(Class ... classes) { + matchClasses = classes; + } + + private static PlacementGuide getGuide(BlockState requiredState) { + for (PlacementGuide guide : PlacementGuide.values()) { + for (Class clazz : guide.matchClasses) { + if (clazz.isInstance(requiredState.getBlock())) { + return guide; + } + } + } + + return DEFAULT; + } + + public static Placement getPlacement(BlockState requiredState) { + switch (getGuide(requiredState)) { + case WALLTORCH, ROD, AMETHYST, SHULKER -> { + return new Placement(((Direction) getPropertyByName(requiredState, "FACING")).getOpposite(), + null, + null); + } + case SLAB -> { + Direction half = requiredState.get(SlabBlock.TYPE) == SlabType.BOTTOM ? Direction.DOWN : Direction.UP; + return new Placement(half, + null, + null); + } + case STAIR -> { + return new Placement(requiredState.get(StairsBlock.FACING), // FIXME before shipping + Vec3d.of(getHalf(requiredState.get(StairsBlock.HALF)).getVector()).multiply(0.25), //getHalf(requiredState.get(StairsBlock.HALF)), + requiredState.get(StairsBlock.FACING)); + } + case TRAPDOOR -> { + return new Placement(getHalf(requiredState.get(TrapdoorBlock.HALF)), + null, //getHalf(requiredState.get(TrapdoorBlock.HALF)), + requiredState.get(StairsBlock.FACING).getOpposite()); + } + case PILLAR -> { + return new Placement(axisToDirection(requiredState.get(PillarBlock.AXIS)), + null, + null, true); + } + case ANVIL -> { + return new Placement(null, + null, + requiredState.get(AnvilBlock.FACING).rotateYCounterclockwise()); + } + case HOPPER, COCOA -> { + return new Placement((Direction) getPropertyByName(requiredState, "FACING"), + null, + null); + } + case WALLMOUNTED -> { + Direction side = switch ((WallMountLocation) getPropertyByName(requiredState, "FACE")) { + case FLOOR -> Direction.DOWN; + case CEILING -> Direction.UP; + default -> ((Direction) getPropertyByName(requiredState, "FACING")).getOpposite(); + }; + + Direction look = getPropertyByName(requiredState, "FACE") == WallMountLocation.WALL ? + null : (Direction) getPropertyByName(requiredState, "FACING"); + + return new Placement(side, + null, + look).setCantPlaceInAir(true); + } +// case GRINDSTONE -> { // Tese are broken +// Direction side = switch ((WallMountLocation) getPropertyByName(requiredState, "FACE")) { +// case FLOOR -> Direction.DOWN; +// case CEILING -> Direction.UP; +// default -> (Direction) getPropertyByName(requiredState, "FACING"); +// }; +// +// Direction look = getPropertyByName(requiredState, "FACE") == WallMountLocation.WALL ? +// null : (Direction) getPropertyByName(requiredState, "FACING"); +// +// return new Placement(Direction.DOWN, // FIXME test +// Vec3d.of(side.getVector()).multiply(0.5), +// look); +// } + case GATE, CAMPFIRE -> { + return new Placement(null, + null, + (Direction) getPropertyByName(requiredState, "FACING")); + } + case BED -> { + if (requiredState.get(BedBlock.PART) != BedPart.FOOT) { + return new Placement(); + } else { + return new Placement(null, null, requiredState.get(BedBlock.FACING)); + } + } + case BELL -> { + Direction side = switch (requiredState.get(BellBlock.ATTACHMENT)) { + case FLOOR -> Direction.DOWN; + case CEILING -> Direction.UP; + default -> requiredState.get(BellBlock.FACING); + }; + + Direction look = requiredState.get(BellBlock.ATTACHMENT) != Attachment.SINGLE_WALL && + requiredState.get(BellBlock.ATTACHMENT) != Attachment.DOUBLE_WALL ? + requiredState.get(BellBlock.FACING) : null; + + return new Placement(side, + null, + look); + } + case DOOR -> { + Direction hinge = requiredState.get(DoorBlock.FACING); + if (requiredState.get(DoorBlock.HINGE) == DoorHinge.RIGHT) { + hinge = hinge.rotateYClockwise(); + } else { + hinge = hinge.rotateYCounterclockwise(); + } + + Vec3d hitModifier = Vec3d.of(hinge.getVector()).multiply(0.25); + return new Placement(Direction.DOWN, + hitModifier, + requiredState.get(DoorBlock.FACING)); + } + case WALLSKULL -> { + return new Placement(requiredState.get(WallSkullBlock.FACING).getOpposite(), null, null); + } + case SKIP -> { + return new Placement(); + } + default -> { // Try to guess how the rest of the blocks are placed. + Direction look = null; + + for (Property prop : requiredState.getProperties()) { + if (prop instanceof DirectionProperty && prop.getName().equalsIgnoreCase("FACING")) { + look = ((Direction) requiredState.get(prop)).getOpposite(); + } + } + + return new Placement(null, null, look); + } + } + } + + private static Direction getHalf(BlockHalf half) { + return half == BlockHalf.TOP ? Direction.UP : Direction.DOWN; + } + + private static Direction axisToDirection(Direction.Axis axis) { + for (Direction direction : Direction.values()) { + if (direction.getAxis() == axis) return direction; + } + + return Direction.DOWN; + } + + private static Comparable getPropertyByName(BlockState state, String name) { + for (Property prop : state.getProperties()) { + if (prop.getName().equalsIgnoreCase(name)) { + return state.get(prop); + } + } + + return null; + } + + public static class Placement { + @Nullable + public final Direction side; + @Nullable + public final Vec3d hitModifier; + @Nullable + public final Direction look; + + boolean sideIsAxis = false; + + boolean cantPlaceInAir = false; + boolean skip; + + public Placement(@Nullable Direction side, @Nullable Vec3d hitModifier, @Nullable Direction look, boolean sideIsAxis) { + this.side = side; + this.hitModifier = hitModifier; + this.look = look; + + this.sideIsAxis = sideIsAxis; + this.skip = false; + } + + public Placement(@Nullable Direction side, @Nullable Vec3d hitModifier, @Nullable Direction look) { + this(side, hitModifier, look, false); + } + + public Placement() { + this(null, null, null, false); + this.skip = true; + } + + public Placement setSideIsAxis(boolean sideIsAxis) { + this.sideIsAxis = sideIsAxis; + + return this; + } + + public Placement setCantPlaceInAir(boolean cantPlaceInAir) { + this.cantPlaceInAir = cantPlaceInAir; + return this; + } + } +} diff --git a/src/main/java/me/aleksilassila/litematica/printer/printer/Printer.java b/src/main/java/me/aleksilassila/litematica/printer/printer/Printer.java index 4b7fc74c2..26860e193 100644 --- a/src/main/java/me/aleksilassila/litematica/printer/printer/Printer.java +++ b/src/main/java/me/aleksilassila/litematica/printer/printer/Printer.java @@ -2,19 +2,20 @@ import fi.dy.masa.litematica.data.DataManager; import fi.dy.masa.litematica.util.InventoryUtils; -import fi.dy.masa.litematica.util.ItemUtils; import fi.dy.masa.litematica.world.SchematicWorldHandler; import fi.dy.masa.litematica.world.WorldSchematic; -import fi.dy.masa.malilib.gui.GuiBase; import me.aleksilassila.litematica.printer.LitematicaMixinMod; import me.aleksilassila.litematica.printer.interfaces.IClientPlayerInteractionManager; import me.aleksilassila.litematica.printer.interfaces.Implementation; -import net.minecraft.block.*; -import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.BlockState; +import net.minecraft.block.ChestBlock; +import net.minecraft.block.FluidBlock; +import net.minecraft.block.Material; import net.minecraft.block.enums.ChestType; import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.player.PlayerInventory; import net.minecraft.inventory.Inventory; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; @@ -35,9 +36,6 @@ public class Printer extends PrinterUtils { int tick = 0; static boolean blockLooks = false; - public boolean lockCamera = false; - - private boolean shouldPlaceWater; private boolean shouldPrintInAir; private boolean shouldReplaceFluids; @@ -60,27 +58,18 @@ public Printer(MinecraftClient client, ClientPlayerEntity playerEntity, ClientWo } public void onTick() { - tick = tick == 0x7fffffff ? 0 : tick + 1; -// lockCamera = false; -// sendQueuedPackets(); - int tickRate = LitematicaMixinMod.PRINT_INTERVAL.getIntegerValue(); -// if (tick % tickRate == tickRate / 2) { -// sendQueuedLookPacket(); -// return; -// } else + + tick = tick == 0x7fffffff ? 0 : tick + 1; if (tick % tickRate != 0) { return; } int range = LitematicaMixinMod.PRINTING_RANGE.getIntegerValue(); -// shouldPlaceWater = LitematicaMixinMod.PRINT_WATER.getBooleanValue(); - shouldPlaceWater = false; - shouldPrintInAir = LitematicaMixinMod.PRINT_IN_AIR.getBooleanValue(); +// shouldPrintInAir = LitematicaMixinMod.PRINT_IN_AIR.getBooleanValue(); shouldReplaceFluids = LitematicaMixinMod.REPLACE_FLUIDS.getBooleanValue(); worldSchematic = SchematicWorldHandler.getSchematicWorld(); - // FIXME if is in range sendQueuedPlacement(); // forEachBlockInRadius: @@ -88,29 +77,48 @@ public void onTick() { for (int x = -range; x < range + 1; x++) { for (int z = -range; z < range + 1; z++) { BlockPos pos = playerEntity.getBlockPos().north(x).west(z).up(y); + BlockState currentState = clientWorld.getBlockState(pos); + BlockState requiredState = worldSchematic.getBlockState(pos); if (!DataManager.getRenderLayerRange().isPositionWithinRange(pos)) continue; - if (shouldSkipPosition(pos)) continue; - if (processBlock(pos)) return; + PlacementGuide.Placement placement = PlacementGuide.getPlacement(requiredState); + ClickGuide.Click click = ClickGuide.shouldClickBlock(requiredState, currentState); + + if (click.click && (click.item == null || playerHasAccessToItem(click.item))) { + sendClick(pos, Vec3d.ofCenter(pos)); + switchToItem(click.item); + return; + } else if (shouldPrintHere(pos, placement) && playerHasAccessToItem(requiredState.getBlock().asItem())) { + boolean doubleChest = requiredState.contains(ChestBlock.CHEST_TYPE) && requiredState.get(ChestBlock.CHEST_TYPE) != ChestType.SINGLE; + Direction side = placement.side == null ? Direction.DOWN : placement.side; + BlockPos neighbor = placement.cantPlaceInAir ? pos.offset(side) : pos; // If placing in air, there's no neighbor + + Vec3d hit = Vec3d.ofCenter(pos).add(Vec3d.of(side.getVector()).multiply(0.5)); + + if (placement.hitModifier != null) { + hit = hit.add(placement.hitModifier); + } + + queuePlacement(neighbor, + side, + hit, + placement.look, + !doubleChest); + + switchToItem(requiredState.getBlock().asItem()); + return; + } } } } } - /** - * @return true if block was placed. - */ - public boolean processBlock(BlockPos pos) { - BlockState currentState = clientWorld.getBlockState(pos); - BlockState requiredState = worldSchematic.getBlockState(pos); + private boolean shouldPrintHere(BlockPos position, PlacementGuide.Placement placement) { + BlockState currentState = clientWorld.getBlockState(position); + BlockState requiredState = worldSchematic.getBlockState(position); - // Check if block should be just clicked (repeaters etc.) - if (shouldClickBlock(currentState, requiredState)) { - addQueuedPacket(pos, Direction.UP, Vec3d.ofCenter(pos), null, false); - - return true; - } + if (placement.skip) return false; // FIXME water and lava // Check if something should be placed in target block @@ -119,148 +127,49 @@ public boolean processBlock(BlockPos pos) { || requiredState.getMaterial().equals(Material.LAVA)) return false; // Check if target block is empty - if (!shouldPlaceWater) - if (!currentState.isAir() && !currentState.contains(FluidBlock.LEVEL)) { - if (!PrinterUtils.isDoubleSlab(requiredState)) return false; - else if (PrinterUtils.isDoubleSlab(currentState)) return false; - } else if (currentState.contains(FluidBlock.LEVEL)) { - if (currentState.get(FluidBlock.LEVEL) == 0 && !shouldReplaceFluids) return false; - } - else { - if (isWaterLogged(requiredState) && isWaterLogged(currentState)) return false; - if (!isWaterLogged(requiredState) && !currentState.isAir()) return false; + if (!currentState.isAir() && !currentState.contains(FluidBlock.LEVEL)) { //current = solid + // Don't skip unfinished double slabs + return PrinterUtils.isDoubleSlab(requiredState) && PrinterUtils.isHalfSlab(currentState); + } else if (currentState.contains(FluidBlock.LEVEL)) { // current = fluid + return currentState.get(FluidBlock.LEVEL) == 0 && !shouldReplaceFluids; } // Check if can be placed in world - if (!requiredState.canPlaceAt(clientWorld, pos)) return false; - - // Check if player is holding right block - Item itemInHand = Implementation.getInventory(playerEntity).getMainHandStack().getItem(); - if (!itemInHand.equals(requiredItemInHand(requiredState, currentState))) { - if (Implementation.getAbilities(playerEntity).creativeMode) { - ItemStack required = new ItemStack(requiredItemInHand(requiredState, currentState)); - BlockEntity te = clientWorld.getBlockEntity(pos); - - // The creative mode pick block with NBT only works correctly - // if the server world doesn't have a TileEntity in that position. - // Otherwise it would try to write whatever that TE is into the picked ItemStack. - if (GuiBase.isCtrlDown() && te != null && clientWorld.isAir(pos)) - { - ItemUtils.storeTEInStack(required, te); - } - - InventoryUtils.setPickedItemToHand(required, client); - client.interactionManager.clickCreativeStack(playerEntity.getStackInHand(Hand.MAIN_HAND), - 36 + Implementation.getInventory(playerEntity).selectedSlot); - - } else { - int slot = getBlockInventorySlot(requiredItemInHand(requiredState, currentState)); - - if (slot == -1) { - return false; - } - - swapHandWithSlot(slot); - } - } - - return placeBlock(pos, requiredState, currentState); + return requiredState.canPlaceAt(clientWorld, position); } - public boolean shouldSkipPosition(BlockPos pos) { - BlockState currentState = clientWorld.getBlockState(pos); - BlockState requiredState = worldSchematic.getBlockState(pos); - - if (shouldClickBlock(currentState, requiredState)) return false; - - // FIXME water and lava - // Check if something should be placed in target block - if (requiredState.isAir() - || requiredState.getMaterial().equals(Material.WATER) - || requiredState.getMaterial().equals(Material.LAVA)) return true; + private void switchToItem(Item item) { + PlayerInventory inv = Implementation.getInventory(playerEntity); + if (inv.getMainHandStack().getItem() == item) return; - // Check if target block is empty - if (!shouldPlaceWater) - if (!currentState.isAir() && !currentState.contains(FluidBlock.LEVEL)) { - if (!PrinterUtils.isDoubleSlab(requiredState)) return true; - else if (PrinterUtils.isDoubleSlab(currentState)) return true; - } else if (currentState.contains(FluidBlock.LEVEL)) { - if (currentState.get(FluidBlock.LEVEL) == 0 && !shouldReplaceFluids) return true; + if (Implementation.getAbilities(playerEntity).creativeMode) { + InventoryUtils.setPickedItemToHand(new ItemStack(item), client); + client.interactionManager.clickCreativeStack(client.player.getStackInHand(Hand.MAIN_HAND), 36 + inv.selectedSlot); + } else { + int slot = 0; + for (int i = 0; i < inv.size(); i++) { + if (inv.getStack(i).getItem() == item && inv.getStack(i).getCount() > 0) + slot = i; } - else { - if (isWaterLogged(requiredState) && isWaterLogged(currentState)) return true; - if (!isWaterLogged(requiredState) && !currentState.isAir()) return true; - } - - // Check if can be placed in world - return !requiredState.canPlaceAt(clientWorld, pos); - } - private int getBlockInventorySlot(Item item) { - Inventory inv = Implementation.getInventory(playerEntity); - - for (int slot = 0; slot < inv.size(); slot++) { - if (inv.getStack(slot).getItem().equals(item)) return slot; + swapHandWithSlot(slot); } - - return -1; } - private boolean placeBlock(BlockPos pos, BlockState state, BlockState currentState) { - Vec3d posVec = Vec3d.ofCenter(pos); - - Direction playerShouldBeFacing = getFacingDirection(state); - Direction.Axis axis = availableAxis(state); - int half = getBlockHalf(state, currentState); - - if (state.getBlock() instanceof SlabBlock) { - System.out.println("Slab half: " + half); - } - - for (Direction side : Direction.values()) { - if (half == 1 && side.equals(Direction.DOWN)) continue; - if (half == 0 && side.equals(Direction.UP)) continue; - if (axis != null && side.getAxis() != axis) continue; - if (isTorchOnWall(state) && playerShouldBeFacing != side) continue; - if (state.getBlock() instanceof HopperBlock && playerShouldBeFacing != side.getOpposite()) continue; - if ((state.getBlock() instanceof AbstractButtonBlock || state.getBlock() instanceof LeverBlock) - && isLeverOnWall(state) - && playerShouldBeFacing != side.getOpposite()) continue; - - BlockPos neighbor = pos.offset(side); - - if (!canBeClicked(neighbor)) { - if (!shouldPrintInAir) continue; - neighbor = pos; - } - - Vec3d hitVec = posVec.add(Vec3d.of(side.getVector()).multiply(0.5)); - - if (half == 1 && !side.equals(Direction.UP)) { - hitVec = hitVec.add(0, 0.25, 0); - } else if (half == 0 && !side.equals(Direction.DOWN)) { - hitVec = hitVec.add(0, -0.25, 0); + private boolean playerHasAccessToItem(Item item) { + if (item == null) return false; + if (Implementation.getAbilities(playerEntity).creativeMode) return true; + else { + Inventory inv = Implementation.getInventory(playerEntity); + for (int i = 0; i < inv.size(); i++) { + if (inv.getStack(i).getItem() == item && inv.getStack(i).getCount() > 0) + return true; } - - boolean doubleChest = state.contains(ChestBlock.CHEST_TYPE) && state.get(ChestBlock.CHEST_TYPE) != ChestType.SINGLE; - addQueuedPacket(neighbor, side, hitVec, playerShouldBeFacing, !doubleChest); - - return true; } return false; } - private Item requiredItemInHand(BlockState requiredState, BlockState currentState) { -// // If block should be waterlogged -// if (!currentState.isAir() && isWaterLogged(requiredState)) -// return Items.WATER_BUCKET; -// else if (requiredState.getBlock().equals(Blocks.WATER)) -// return Items.WATER_BUCKET; -// else - return new ItemStack(requiredState.getBlock()).getItem(); - } - private VoxelShape getOutlineShape(BlockPos pos) { return getState(pos).getOutlineShape(clientWorld, pos); @@ -293,7 +202,18 @@ else if (!Queue.useShift && wasSneaking) blockLooks = false; } - public void addQueuedPacket(BlockPos neighbor, Direction side, Vec3d hitVec, Direction playerShouldBeFacing, boolean useShift) { + private void sendClick(BlockPos neighbor, Vec3d hitVec) { + ((IClientPlayerInteractionManager) client.interactionManager).rightClickBlock(neighbor, + Direction.UP, hitVec); + } + + /** + * Adds a placement packet to queue + * @param neighbor Neighboring block to be clicked + * @param side Direction where the neighboring block is + * @param hitVec Position where the player would click + */ + private void queuePlacement(BlockPos neighbor, Direction side, Vec3d hitVec, Direction playerShouldBeFacing, boolean useShift) { // Skip if last packet hasn't been sent yet. if (Queue.neighbor != null) return; @@ -308,7 +228,7 @@ public void addQueuedPacket(BlockPos neighbor, Direction side, Vec3d hitVec, Dir Queue.playerShouldBeFacing = playerShouldBeFacing; Queue.neighbor = neighbor; - Queue.side = side; + Queue.side = side == null ? Direction.DOWN : side; Queue.hitVec = hitVec; Queue.useShift = useShift; } diff --git a/src/main/java/me/aleksilassila/litematica/printer/printer/UpdateChecker.java b/src/main/java/me/aleksilassila/litematica/printer/printer/UpdateChecker.java index 9b9b953cd..96909bed0 100644 --- a/src/main/java/me/aleksilassila/litematica/printer/printer/UpdateChecker.java +++ b/src/main/java/me/aleksilassila/litematica/printer/printer/UpdateChecker.java @@ -9,7 +9,7 @@ import java.util.Scanner; public class UpdateChecker { - public static final String version = "v1.9"; + public static final String version = "v2.0"; public static String getPrinterVersion() { try (InputStream inputStream = new URL("https://api.github.com/repos/aleksilassila/litematica-printer/tags").openStream(); Scanner scanner = new Scanner(inputStream)) {