From 98009a1923e1116379a69a81782ac4ead7622bf0 Mon Sep 17 00:00:00 2001 From: Aleksi Lassila Date: Thu, 22 Jun 2023 18:09:24 +0300 Subject: [PATCH] Temporary release for 1.20.1 --- settings.gradle | 1 + .../guides/placement/RailGuesserGuide.java | 12 +- v1_20/build.gradle.kts | 88 +++++++++++ v1_20/gradle.properties | 13 ++ .../printer/v1_20/ActionHandler.java | 57 +++++++ .../litematica/printer/v1_20/BlockHelper.java | 31 ++++ .../printer/v1_20/LitematicaMixinMod.java | 58 ++++++++ .../litematica/printer/v1_20/Printer.java | 107 ++++++++++++++ .../printer/v1_20/SchematicBlockState.java | 42 ++++++ .../printer/v1_20/UpdateChecker.java | 49 ++++++ .../printer/v1_20/actions/Action.java | 8 + .../printer/v1_20/actions/InteractAction.java | 33 +++++ .../printer/v1_20/actions/PrepareAction.java | 109 ++++++++++++++ .../v1_20/actions/ReleaseShiftAction.java | 13 ++ .../printer/v1_20/guides/Guide.java | 129 ++++++++++++++++ .../printer/v1_20/guides/Guides.java | 83 +++++++++++ .../printer/v1_20/guides/SkipGuide.java | 37 +++++ .../interaction/CampfireExtinguishGuide.java | 34 +++++ .../guides/interaction/CycleStateGuide.java | 45 ++++++ .../guides/interaction/EnderEyeGuide.java | 33 +++++ .../interaction/FlowerPotFillGuide.java | 40 +++++ .../guides/interaction/InteractionGuide.java | 49 ++++++ .../guides/interaction/LightCandleGuide.java | 36 +++++ .../guides/interaction/LogStrippingGuide.java | 47 ++++++ .../guides/interaction/TillingGuide.java | 39 +++++ .../BlockIndifferentGuesserGuide.java | 42 ++++++ .../placement/BlockReplacementGuide.java | 76 ++++++++++ .../v1_20/guides/placement/ChestGuide.java | 92 ++++++++++++ .../guides/placement/FallingBlockGuide.java | 37 +++++ .../v1_20/guides/placement/FarmlandGuide.java | 29 ++++ .../guides/placement/FlowerPotGuide.java | 20 +++ .../placement/GeneralPlacementGuide.java | 119 +++++++++++++++ .../v1_20/guides/placement/GuesserGuide.java | 110 ++++++++++++++ .../v1_20/guides/placement/LogGuide.java | 54 +++++++ .../guides/placement/PlacementGuide.java | 139 ++++++++++++++++++ .../PropertySpecificGuesserGuide.java | 52 +++++++ .../guides/placement/RailGuesserGuide.java | 128 ++++++++++++++++ .../guides/placement/RotatingBlockGuide.java | 58 ++++++++ .../v1_20/guides/placement/SlabGuide.java | 48 ++++++ .../v1_20/guides/placement/TorchGuide.java | 31 ++++ .../v1_20/implementation/BlockHelperImpl.java | 14 ++ .../PrinterPlacementContext.java | 59 ++++++++ .../actions/InteractActionImpl.java | 20 +++ .../mixin/MixinClientPlayerEntity.java | 102 +++++++++++++ .../printer/v1_20/mixin/AxeItemAccessor.java | 20 +++ .../printer/v1_20/mixin/ConfigsMixin.java | 35 +++++ .../printer/v1_20/mixin/GuiConfigsMixin.java | 61 ++++++++ .../v1_20/mixin/InputHandlerMixin.java | 25 ++++ .../v1_20/mixin/PlayerMoveC2SPacketMixin.java | 36 +++++ .../src/main/resources/assets/modid/icon.png | Bin 0 -> 453 bytes v1_20/src/main/resources/fabric.mod.json | 38 +++++ ...ematica-printer-implementation.mixins.json | 14 ++ .../resources/litematica-printer.mixins.json | 18 +++ 53 files changed, 2664 insertions(+), 6 deletions(-) create mode 100644 v1_20/build.gradle.kts create mode 100644 v1_20/gradle.properties create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/ActionHandler.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/BlockHelper.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/LitematicaMixinMod.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/Printer.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/SchematicBlockState.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/UpdateChecker.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/actions/Action.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/actions/InteractAction.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/actions/PrepareAction.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/actions/ReleaseShiftAction.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/Guide.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/Guides.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/SkipGuide.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/interaction/CampfireExtinguishGuide.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/interaction/CycleStateGuide.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/interaction/EnderEyeGuide.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/interaction/FlowerPotFillGuide.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/interaction/InteractionGuide.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/interaction/LightCandleGuide.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/interaction/LogStrippingGuide.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/interaction/TillingGuide.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/BlockIndifferentGuesserGuide.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/BlockReplacementGuide.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/ChestGuide.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/FallingBlockGuide.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/FarmlandGuide.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/FlowerPotGuide.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/GeneralPlacementGuide.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/GuesserGuide.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/LogGuide.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/PlacementGuide.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/PropertySpecificGuesserGuide.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/RailGuesserGuide.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/RotatingBlockGuide.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/SlabGuide.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/TorchGuide.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/implementation/BlockHelperImpl.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/implementation/PrinterPlacementContext.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/implementation/actions/InteractActionImpl.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/implementation/mixin/MixinClientPlayerEntity.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/mixin/AxeItemAccessor.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/mixin/ConfigsMixin.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/mixin/GuiConfigsMixin.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/mixin/InputHandlerMixin.java create mode 100644 v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/mixin/PlayerMoveC2SPacketMixin.java create mode 100644 v1_20/src/main/resources/assets/modid/icon.png create mode 100644 v1_20/src/main/resources/fabric.mod.json create mode 100644 v1_20/src/main/resources/litematica-printer-implementation.mixins.json create mode 100644 v1_20/src/main/resources/litematica-printer.mixins.json diff --git a/settings.gradle b/settings.gradle index cd0dff3c0..a0b169fe3 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,6 +8,7 @@ pluginManagement { gradlePluginPortal() } } +include 'v1_20' include 'v1_19_4' include 'v1_19_3' include 'v1_19' diff --git a/v1_19_4/src/main/java/me/aleksilassila/litematica/printer/v1_19_4/guides/placement/RailGuesserGuide.java b/v1_19_4/src/main/java/me/aleksilassila/litematica/printer/v1_19_4/guides/placement/RailGuesserGuide.java index 6e581a579..e9be5c9a1 100644 --- a/v1_19_4/src/main/java/me/aleksilassila/litematica/printer/v1_19_4/guides/placement/RailGuesserGuide.java +++ b/v1_19_4/src/main/java/me/aleksilassila/litematica/printer/v1_19_4/guides/placement/RailGuesserGuide.java @@ -31,12 +31,12 @@ protected boolean statesEqual(BlockState resultState, BlockState targetState) { if (!wouldConnectCorrectly()) return false; // if (wouldBlockAnotherConnection()) return false; /*TODO: Fully working rail guesser - * If has a neighbor that: - * - Has not been placed yet - * - OR Has been placed but can change shape - * - AND this placement should connect to only one rail, that is not the neighbor - * Then return false - * */ + * If has a neighbor that: + * - Has not been placed yet + * - OR Has been placed but can change shape + * - AND this placement should connect to only one rail, that is not the neighbor + * Then return false + * */ if (getRailShape(resultState).isPresent()) { if (Arrays.stream(STRAIGHT_RAIL_SHAPES).anyMatch(shape -> shape == getRailShape(resultState).orElse(null))) { diff --git a/v1_20/build.gradle.kts b/v1_20/build.gradle.kts new file mode 100644 index 000000000..c3b72eeb4 --- /dev/null +++ b/v1_20/build.gradle.kts @@ -0,0 +1,88 @@ +plugins { + id("fabric-loom").version("1.0-SNAPSHOT") + id("maven-publish") +} + +java.sourceCompatibility = JavaVersion.VERSION_17 +java.targetCompatibility = JavaVersion.VERSION_17 + +val archives_base_name: String by project +val minecraft_version: String by project +val yarn_mappings: String by project +val loader_version: String by project +val fabric_version: String by project +val malilib_version: String by project +val litematica_projectid: String by project +val litematica_fileid: String by project + +val mod_version: String by project + +dependencies { +// implementation(project(":common")) + minecraft("com.mojang:minecraft:${minecraft_version}") + mappings("net.fabricmc:yarn:${yarn_mappings}:v2") + + modImplementation("net.fabricmc:fabric-loader:${loader_version}") + modImplementation("net.fabricmc.fabric-api:fabric-api:${fabric_version}") + modImplementation("fi.dy.masa.malilib:malilib-fabric-${malilib_version}") + modImplementation("curse.maven:litematica-${litematica_projectid}:${litematica_fileid}") +} + +repositories { + maven("https://masa.dy.fi/maven") + maven("https://www.cursemaven.com") +} + +// Process resources +tasks.withType { + inputs.property("version", mod_version) + + filesMatching("fabric.mod.json") { + expand(mapOf("version" to mod_version)) + } +} + +val sourceModule = "v1_19_4" +val targetModules = arrayOf("v1_19", "v1_18", "v1_17") + +fun copyFile(source: File) { + for (targetModule in targetModules) { + val destination = file(source.absolutePath.replace(sourceModule, targetModule)) + println("Copying ${source.absolutePath} to ${destination.absolutePath}") + destination.parentFile.mkdirs() + source.copyTo(destination, true) + destination.writeText(destination.readText().replace(sourceModule, targetModule)) + } +} + +fun deleteOldFiles(sourceBase: File) { + for (targetModule in targetModules) { + val targetBase = file(sourceBase.absolutePath.replace(sourceModule, targetModule)) + + for (file in targetBase.listFiles()) { + if (file.name.equals("implementation")) continue + println("Deleting recursively ${file.absolutePath}") + file.deleteRecursively() + } + } +} + +val syncImplementations = tasks.create("syncImplementations") { + doFirst { + val sourceStart = + this.project.projectDir.absolutePath + "/src/main/java/me/aleksilassila/litematica/printer/" + sourceModule + val sourceDir = file(sourceStart) + + deleteOldFiles(sourceDir) + + for (sourceFile in sourceDir.listFiles()) { + if (sourceFile.name.equals("implementation")) continue + + sourceFile.walk() + .filter { it.isFile } + .forEach { + copyFile(it) + } + } + } +} diff --git a/v1_20/gradle.properties b/v1_20/gradle.properties new file mode 100644 index 000000000..eb468a4ae --- /dev/null +++ b/v1_20/gradle.properties @@ -0,0 +1,13 @@ +# Done to increase the memory available to gradle. +org.gradle.jvmargs=-Xmx1G +# https://masa.dy.fi/maven/fi/dy/masa/malilib/ +malilib_version=1.20.1:0.16.0 +# https://www.curseforge.com/minecraft/mc-mods/litematica/files +litematica_fileid=4593296 +litematica_projectid=308892 +# Fabric Properties: https://fabricmc.net/develop/ +minecraft_version=1.20.1 +yarn_mappings=1.20.1+build.5 +loader_version=0.14.21 +#Fabric api +fabric_version=0.84.0+1.20.1 diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/ActionHandler.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/ActionHandler.java new file mode 100644 index 000000000..c205ae488 --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/ActionHandler.java @@ -0,0 +1,57 @@ +package me.aleksilassila.litematica.printer.v1_20; + +import me.aleksilassila.litematica.printer.v1_20.actions.Action; +import me.aleksilassila.litematica.printer.v1_20.actions.PrepareAction; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; + +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +public class ActionHandler { + private final MinecraftClient client; + private final ClientPlayerEntity player; + + private final Queue actionQueue = new LinkedList<>(); + public PrepareAction lookAction = null; + + public ActionHandler(MinecraftClient client, ClientPlayerEntity player) { + this.client = client; + this.player = player; + } + + private int tick = 0; + + public void onGameTick() { + int tickRate = LitematicaMixinMod.PRINTING_INTERVAL.getIntegerValue(); + + tick = tick % tickRate == tickRate - 1 ? 0 : tick + 1; + if (tick % tickRate != 0) { + return; + } + + Action nextAction = actionQueue.poll(); + if (nextAction != null) { + if (LitematicaMixinMod.DEBUG) System.out.println("Sending action " + nextAction); + nextAction.send(client, player); + } else { + lookAction = null; + } + } + + public boolean acceptsActions() { + return actionQueue.isEmpty(); + } + + public void addActions(Action... actions) { + if (!acceptsActions()) return; + + for (Action action : actions) { + if (action instanceof PrepareAction) + lookAction = (PrepareAction) action; + } + + actionQueue.addAll(List.of(actions)); + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/BlockHelper.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/BlockHelper.java new file mode 100644 index 000000000..488b39ca6 --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/BlockHelper.java @@ -0,0 +1,31 @@ +package me.aleksilassila.litematica.printer.v1_20; + +import net.minecraft.block.*; +import net.minecraft.item.Item; +import net.minecraft.item.Items; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +abstract public class BlockHelper { + public static List> interactiveBlocks = new ArrayList<>(Arrays.asList( + AbstractChestBlock.class, AbstractFurnaceBlock.class, CraftingTableBlock.class, LeverBlock.class, + DoorBlock.class, TrapdoorBlock.class, BedBlock.class, RedstoneWireBlock.class, ScaffoldingBlock.class, + HopperBlock.class, EnchantingTableBlock.class, NoteBlock.class, JukeboxBlock.class, CakeBlock.class, + FenceGateBlock.class, BrewingStandBlock.class, DragonEggBlock.class, CommandBlock.class, + BeaconBlock.class, AnvilBlock.class, ComparatorBlock.class, RepeaterBlock.class, + DropperBlock.class, DispenserBlock.class, ShulkerBoxBlock.class, LecternBlock.class, + FlowerPotBlock.class, BarrelBlock.class, BellBlock.class, SmithingTableBlock.class, + LoomBlock.class, CartographyTableBlock.class, GrindstoneBlock.class, + StonecutterBlock.class, AbstractSignBlock.class, AbstractCandleBlock.class)); + + public static final Item[] SHOVEL_ITEMS = new Item[]{ + Items.NETHERITE_SHOVEL, + Items.DIAMOND_SHOVEL, + Items.GOLDEN_SHOVEL, + Items.IRON_SHOVEL, + Items.STONE_SHOVEL, + Items.WOODEN_SHOVEL + }; +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/LitematicaMixinMod.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/LitematicaMixinMod.java new file mode 100644 index 000000000..f48b46125 --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/LitematicaMixinMod.java @@ -0,0 +1,58 @@ +package me.aleksilassila.litematica.printer.v1_20; + +import com.google.common.collect.ImmutableList; +import fi.dy.masa.litematica.config.Configs; +import fi.dy.masa.litematica.config.Hotkeys; +import fi.dy.masa.malilib.config.IConfigBase; +import fi.dy.masa.malilib.config.options.ConfigBoolean; +import fi.dy.masa.malilib.config.options.ConfigDouble; +import fi.dy.masa.malilib.config.options.ConfigHotkey; +import fi.dy.masa.malilib.config.options.ConfigInteger; +import fi.dy.masa.malilib.hotkeys.KeyCallbackToggleBooleanConfigWithMessage; +import fi.dy.masa.malilib.hotkeys.KeybindSettings; +import net.fabricmc.api.ModInitializer; + +import java.util.List; + +public class LitematicaMixinMod implements ModInitializer { + + public static Printer printer; + public static boolean DEBUG = false; + // Config settings + public static final ConfigInteger PRINTING_INTERVAL = new ConfigInteger("printingInterval", 12, 1, 40, "Printing interval. Lower values mean faster printing speed.\nIf the printer creates \"ghost blocks\" or blocks are facing the wrong way, raise this value."); + public static final ConfigDouble PRINTING_RANGE = new ConfigDouble("printingRange", 5, 2.5, 5, "Printing block place range\nLower values are recommended for servers."); + // public static final ConfigBoolean PRINT_WATER = new ConfigBoolean("printWater", false, "Whether the printer should place water\n source blocks or make blocks waterlogged."); +// public static final ConfigBoolean PRINT_IN_AIR = new ConfigBoolean("printInAir", true, "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_SOURCE_BLOCKS = new ConfigBoolean("replaceFluidSourceBlocks", true, "Whether or not fluid source blocks should be replaced by the printer."); + public static final ConfigBoolean STRIP_LOGS = new ConfigBoolean("stripLogs", true, "Whether or not the printer should use normal logs if stripped\nversions are not available and then strip them with an axe."); + + public static ImmutableList getConfigList() { + List list = new java.util.ArrayList<>(Configs.Generic.OPTIONS); + list.add(PRINT_MODE); + list.add(PRINTING_INTERVAL); + list.add(PRINTING_RANGE); +// list.add(PRINT_IN_AIR); + list.add(REPLACE_FLUIDS_SOURCE_BLOCKS); + list.add(STRIP_LOGS); + + return ImmutableList.copyOf(list); + } + + // Hotkeys + public static final ConfigHotkey PRINT = new ConfigHotkey("print", "V", KeybindSettings.PRESS_ALLOWEXTRA_EMPTY, "Prints while pressed"); + public static final ConfigHotkey TOGGLE_PRINTING_MODE = new ConfigHotkey("togglePrintingMode", "CAPS_LOCK", KeybindSettings.PRESS_ALLOWEXTRA_EMPTY, "Allows quickly toggling on/off Printing mode"); + + public static List getHotkeyList() { + List list = new java.util.ArrayList<>(Hotkeys.HOTKEY_LIST); + list.add(PRINT); + list.add(TOGGLE_PRINTING_MODE); + + return ImmutableList.copyOf(list); + } + + @Override + public void onInitialize() { + TOGGLE_PRINTING_MODE.getKeybind().setCallback(new KeyCallbackToggleBooleanConfigWithMessage(PRINT_MODE)); + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/Printer.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/Printer.java new file mode 100644 index 000000000..738292ab1 --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/Printer.java @@ -0,0 +1,107 @@ +package me.aleksilassila.litematica.printer.v1_20; + +import fi.dy.masa.litematica.data.DataManager; +import fi.dy.masa.litematica.util.RayTraceUtils; +import fi.dy.masa.litematica.world.SchematicWorldHandler; +import fi.dy.masa.litematica.world.WorldSchematic; +import me.aleksilassila.litematica.printer.v1_20.actions.Action; +import me.aleksilassila.litematica.printer.v1_20.guides.Guide; +import me.aleksilassila.litematica.printer.v1_20.guides.Guides; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.entity.player.PlayerAbilities; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public class Printer { + @NotNull + public final ClientPlayerEntity player; + + public final ActionHandler actionHandler; + + private final Guides interactionGuides = new Guides(); + + public Printer(@NotNull MinecraftClient client, @NotNull ClientPlayerEntity player) { + this.player = player; + + this.actionHandler = new ActionHandler(client, player); + } + + public boolean onGameTick() { + WorldSchematic worldSchematic = SchematicWorldHandler.getSchematicWorld(); + + if (!actionHandler.acceptsActions()) return false; + + if (worldSchematic == null) return false; + + if (!LitematicaMixinMod.PRINT_MODE.getBooleanValue() && !LitematicaMixinMod.PRINT.getKeybind().isPressed()) + return false; + + PlayerAbilities abilities = player.getAbilities(); + if (!abilities.allowModifyWorld) + return false; + + List positions = getReachablePositions(); + findBlock: + for (BlockPos position : positions) { + SchematicBlockState state = new SchematicBlockState(player.getWorld(), worldSchematic, position); + if (state.targetState.equals(state.currentState) || state.targetState.isAir()) continue; + + Guide[] guides = interactionGuides.getInteractionGuides(state); + + BlockHitResult result = RayTraceUtils.traceToSchematicWorld(player, 10, true, true); + boolean isCurrentlyLookingSchematic = result != null && result.getBlockPos().equals(position); + + for (Guide guide : guides) { + if (guide.canExecute(player)) { + System.out.println("Executing " + guide + " for " + state); + List actions = guide.execute(player); + actionHandler.addActions(actions.toArray(Action[]::new)); + return true; + } + if (guide.skipOtherGuides()) continue findBlock; + } + } + + return false; + } + + private List getReachablePositions() { + int maxReach = (int) Math.ceil(LitematicaMixinMod.PRINTING_RANGE.getDoubleValue()); + double maxReachSquared = MathHelper.square(LitematicaMixinMod.PRINTING_RANGE.getDoubleValue()); + + ArrayList positions = new ArrayList<>(); + + for (int y = -maxReach; y < maxReach + 1; y++) { + for (int x = -maxReach; x < maxReach + 1; x++) { + for (int z = -maxReach; z < maxReach + 1; z++) { + BlockPos blockPos = player.getBlockPos().north(x).west(z).up(y); + + if (!DataManager.getRenderLayerRange().isPositionWithinRange(blockPos)) continue; + if (this.player.getEyePos().squaredDistanceTo(Vec3d.ofCenter(blockPos)) > maxReachSquared) { + continue; + } + + positions.add(blockPos); + } + } + } + + return positions.stream() + .filter(p -> { + Vec3d vec = Vec3d.ofCenter(p); + return this.player.getPos().squaredDistanceTo(vec) > 1 && this.player.getEyePos().squaredDistanceTo(vec) > 1; + }) + .sorted((a, b) -> { + double aDistance = this.player.getPos().squaredDistanceTo(Vec3d.ofCenter(a)); + double bDistance = this.player.getPos().squaredDistanceTo(Vec3d.ofCenter(b)); + return Double.compare(aDistance, bDistance); + }).toList(); + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/SchematicBlockState.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/SchematicBlockState.java new file mode 100644 index 000000000..fc970fd08 --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/SchematicBlockState.java @@ -0,0 +1,42 @@ +package me.aleksilassila.litematica.printer.v1_20; + +import fi.dy.masa.litematica.world.WorldSchematic; +import net.minecraft.block.BlockState; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.World; + +public class SchematicBlockState { + public final World world; + public final WorldSchematic schematic; + + public final BlockPos blockPos; + + public final BlockState targetState; + public final BlockState currentState; + + public SchematicBlockState(World world, WorldSchematic schematic, BlockPos blockPos) { + this.world = world; + this.schematic = schematic; + + this.blockPos = blockPos; + + this.targetState = schematic.getBlockState(blockPos); + this.currentState = world.getBlockState(blockPos); + } + + public SchematicBlockState offset(Direction direction) { + return new SchematicBlockState(world, schematic, blockPos.offset(direction)); + } + + @Override + public String toString() { + return "SchematicBlockState{" + + "world=" + world + + ", schematic=" + schematic + + ", blockPos=" + blockPos + + ", targetState=" + targetState + + ", currentState=" + currentState + + '}'; + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/UpdateChecker.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/UpdateChecker.java new file mode 100644 index 000000000..9ea41e510 --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/UpdateChecker.java @@ -0,0 +1,49 @@ +package me.aleksilassila.litematica.printer.v1_20; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.apache.commons.io.IOUtils; + +import java.io.BufferedReader; +import java.io.File; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.Scanner; + +public class UpdateChecker { + public static final String version = "v3.2.1"; + + // Try to get this to work at some point +// static { +// try (InputStream in = UpdateChecker.class.getResourceAsStream("/fabric.mod.json")) { +// String jsonString = IOUtils.toString(in, StandardCharsets.UTF_8); +// JsonObject json = JsonParser.parseString(jsonString).getAsJsonObject(); +// System.out.println("JSON object: " + json); +// System.out.println("Raw json: " + jsonString); +// System.out.println("File: " + new File(UpdateChecker.class.getResource("/fabric.mod.json").getFile())); +// String version = json.get("version").getAsString(); +// System.out.println("Reading fabric.mod.json"); +// System.out.println("Parsed version: " + version); +// } catch (Exception e) { +// e.printStackTrace(); +// } +// } + + @SuppressWarnings("deprecation") + public static String getPrinterVersion() { + try (InputStream inputStream = new URL("https://api.github.com/repos/aleksilassila/litematica-printer/tags").openStream(); Scanner scanner = new Scanner(inputStream)) { + if (scanner.hasNext()) { + JsonArray tags = new JsonParser().parse(scanner.next()).getAsJsonArray(); + return ((JsonObject) tags.get(0)).get("name").getAsString(); + } + } catch (Exception exception) { + System.out.println("Cannot look for updates: " + exception.getMessage()); + } + + return ""; + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/actions/Action.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/actions/Action.java new file mode 100644 index 000000000..956d2df2a --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/actions/Action.java @@ -0,0 +1,8 @@ +package me.aleksilassila.litematica.printer.v1_20.actions; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; + +public abstract class Action { + abstract public void send(MinecraftClient client, ClientPlayerEntity player); +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/actions/InteractAction.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/actions/InteractAction.java new file mode 100644 index 000000000..d74f814b5 --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/actions/InteractAction.java @@ -0,0 +1,33 @@ +package me.aleksilassila.litematica.printer.v1_20.actions; + +import me.aleksilassila.litematica.printer.v1_20.LitematicaMixinMod; +import me.aleksilassila.litematica.printer.v1_20.implementation.PrinterPlacementContext; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.util.Hand; +import net.minecraft.util.hit.BlockHitResult; + +abstract public class InteractAction extends Action { + public final PrinterPlacementContext context; + + public InteractAction(PrinterPlacementContext context) { + this.context = context; + } + + protected abstract void interact(MinecraftClient client, ClientPlayerEntity player, Hand hand, BlockHitResult hitResult); + + @Override + public void send(MinecraftClient client, ClientPlayerEntity player) { + interact(client, player, Hand.MAIN_HAND, context.hitResult); + + if (LitematicaMixinMod.DEBUG) + System.out.println("InteractAction.send: Blockpos: " + context.getBlockPos() + " Side: " + context.getSide() + " HitPos: " + context.getHitPos()); + } + + @Override + public String toString() { + return "InteractAction{" + + "context=" + context + + '}'; + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/actions/PrepareAction.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/actions/PrepareAction.java new file mode 100644 index 000000000..e809baefa --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/actions/PrepareAction.java @@ -0,0 +1,109 @@ +package me.aleksilassila.litematica.printer.v1_20.actions; + +import me.aleksilassila.litematica.printer.v1_20.implementation.PrinterPlacementContext; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.network.packet.c2s.play.ClientCommandC2SPacket; +import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket; +import net.minecraft.util.Hand; +import net.minecraft.util.math.Direction; + +public class PrepareAction extends Action { +// public final Direction lookDirection; +// public final boolean requireSneaking; +// public final Item item; + +// public PrepareAction(Direction lookDirection, boolean requireSneaking, Item item) { +// this.lookDirection = lookDirection; +// this.requireSneaking = requireSneaking; +// this.item = item; +// } +// +// public PrepareAction(Direction lookDirection, boolean requireSneaking, BlockState requiredState) { +// this(lookDirection, requireSneaking, requiredState.getBlock().asItem()); +// } + + public final PrinterPlacementContext context; + + public boolean modifyYaw = true; + public boolean modifyPitch = true; + public float yaw = 0; + public float pitch = 0; + + public PrepareAction(PrinterPlacementContext context) { + this.context = context; + + Direction lookDirection = context.lookDirection; + + if (lookDirection != null && lookDirection.getAxis().isHorizontal()) { + this.yaw = lookDirection.asRotation(); + } else { + this.modifyYaw = false; + } + + if (lookDirection == Direction.UP) { + this.pitch = -90; + } else if (lookDirection == Direction.DOWN) { + this.pitch = 90; + } else if (lookDirection != null) { + this.pitch = 0; + } else { + this.modifyPitch = false; + } + } + + public PrepareAction(PrinterPlacementContext context, float yaw, float pitch) { + this.context = context; + + this.yaw = yaw; + this.pitch = pitch; + } + + @Override + public void send(MinecraftClient client, ClientPlayerEntity player) { + ItemStack itemStack = context.getStack(); + int slot = context.requiredItemSlot; + + if (itemStack != null) { + PlayerInventory inventory = player.getInventory(); + + // This thing is straight from MinecraftClient#doItemPick() + if (player.getAbilities().creativeMode) { + inventory.addPickBlock(itemStack); + client.interactionManager.clickCreativeStack(player.getStackInHand(Hand.MAIN_HAND), 36 + inventory.selectedSlot); + } else if (slot != -1) { + if (PlayerInventory.isValidHotbarIndex(slot)) { + inventory.selectedSlot = slot; + } else { + client.interactionManager.pickFromInventory(slot); + } + } + } + + if (modifyPitch || modifyYaw) { + float yaw = modifyYaw ? this.yaw : player.getYaw(); + float pitch = modifyPitch ? this.pitch : player.getPitch(); + + PlayerMoveC2SPacket packet = new PlayerMoveC2SPacket.Full(player.getX(), player.getY(), player.getZ(), yaw, pitch, player.isOnGround()); + + player.networkHandler.sendPacket(packet); + } + + if (context.shouldSneak) { + player.input.sneaking = true; + player.networkHandler.sendPacket(new ClientCommandC2SPacket(player, ClientCommandC2SPacket.Mode.PRESS_SHIFT_KEY)); + } else { + player.input.sneaking = false; + player.networkHandler.sendPacket(new ClientCommandC2SPacket(player, ClientCommandC2SPacket.Mode.RELEASE_SHIFT_KEY)); + } + } + + @Override + public String toString() { + return "PrepareAction{" + + "context=" + context + + '}'; + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/actions/ReleaseShiftAction.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/actions/ReleaseShiftAction.java new file mode 100644 index 000000000..685e52243 --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/actions/ReleaseShiftAction.java @@ -0,0 +1,13 @@ +package me.aleksilassila.litematica.printer.v1_20.actions; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.network.packet.c2s.play.ClientCommandC2SPacket; + +public class ReleaseShiftAction extends Action { + @Override + public void send(MinecraftClient client, ClientPlayerEntity player) { + player.input.sneaking = false; + player.networkHandler.sendPacket(new ClientCommandC2SPacket(player, ClientCommandC2SPacket.Mode.RELEASE_SHIFT_KEY)); + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/Guide.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/Guide.java new file mode 100644 index 000000000..e85fe900b --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/Guide.java @@ -0,0 +1,129 @@ +package me.aleksilassila.litematica.printer.v1_20.guides; + +import me.aleksilassila.litematica.printer.v1_20.SchematicBlockState; +import me.aleksilassila.litematica.printer.v1_20.actions.Action; +import me.aleksilassila.litematica.printer.v1_20.implementation.BlockHelperImpl; +import net.minecraft.block.BlockState; +import net.minecraft.block.CoralBlock; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.state.property.Properties; +import net.minecraft.state.property.Property; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Optional; + +abstract public class Guide extends BlockHelperImpl { + protected final SchematicBlockState state; + protected final BlockState currentState; + protected final BlockState targetState; + + public Guide(SchematicBlockState state) { + this.state = state; + + this.currentState = state.currentState; + this.targetState = state.targetState; + } + + protected boolean playerHasRightItem(ClientPlayerEntity player) { + return getRequiredItemStackSlot(player) != -1; + } + + protected int getSlotWithItem(ClientPlayerEntity player, ItemStack itemStack) { + PlayerInventory inventory = player.getInventory(); + + for (int i = 0; i < inventory.main.size(); ++i) { + if (itemStack.isEmpty() && inventory.main.get(i).isOf(itemStack.getItem())) return i; + if (!inventory.main.get(i).isEmpty() && ItemStack.areItemsEqual(inventory.main.get(i), itemStack)) { + return i; + } + } + + return -1; + } + + protected int getRequiredItemStackSlot(ClientPlayerEntity player) { + if (player.getAbilities().creativeMode) { + return player.getInventory().selectedSlot; + } + + Optional requiredItem = getRequiredItem(player); + if (requiredItem.isEmpty()) return -1; + + return getSlotWithItem(player, requiredItem.get()); + } + + public boolean canExecute(ClientPlayerEntity player) { + if (!playerHasRightItem(player)) return false; + + BlockState targetState = state.targetState; + BlockState currentState = state.currentState; + + return !statesEqual(targetState, currentState); + } + + abstract public @NotNull List execute(ClientPlayerEntity player); + + abstract protected @NotNull List getRequiredItems(); + + /** + * Returns the first required item that player has access to, + * or empty if the items are inaccessible. + */ + protected Optional getRequiredItem(ClientPlayerEntity player) { + List requiredItems = getRequiredItems(); + + for (ItemStack requiredItem : requiredItems) { + if (player.getAbilities().creativeMode) return Optional.of(requiredItem); + + int slot = getSlotWithItem(player, requiredItem); + if (slot > -1) + return Optional.of(requiredItem); + } + + return Optional.empty(); + } + + protected boolean statesEqualIgnoreProperties(BlockState state1, BlockState state2, Property... propertiesToIgnore) { + if (state1.getBlock() != state2.getBlock()) return false; + + loop: + for (Property property : state1.getProperties()) { + if (property == Properties.WATERLOGGED && !(state1.getBlock() instanceof CoralBlock)) continue; + + for (Property ignoredProperty : propertiesToIgnore) { + if (property == ignoredProperty) continue loop; + } + + try { + if (state1.get(property) != state2.get(property)) { + return false; + } + } catch (Exception e) { + return false; + } + } + + return true; + } + + protected static > Optional getProperty(BlockState blockState, Property property) { + if (blockState.contains(property)) { + return Optional.of(blockState.get(property)); + } + return Optional.empty(); + } + + /** + * Returns true if + */ + protected boolean statesEqual(BlockState state1, BlockState state2) { + return statesEqualIgnoreProperties(state1, state2); + } + + public boolean skipOtherGuides() { + return false; + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/Guides.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/Guides.java new file mode 100644 index 000000000..0d826384f --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/Guides.java @@ -0,0 +1,83 @@ +package me.aleksilassila.litematica.printer.v1_20.guides; + +import me.aleksilassila.litematica.printer.v1_20.SchematicBlockState; +import me.aleksilassila.litematica.printer.v1_20.guides.interaction.*; +import me.aleksilassila.litematica.printer.v1_20.guides.placement.*; +import me.aleksilassila.litematica.printer.v1_20.guides.interaction.*; +import me.aleksilassila.litematica.printer.v1_20.guides.placement.*; +import net.minecraft.block.*; +import net.minecraft.util.Pair; + +import java.util.ArrayList; + +public class Guides { + protected final static ArrayList, Class[]>> guides = new ArrayList<>(); + + @SafeVarargs + protected static void registerGuide(Class guideClass, Class... blocks) { + guides.add(new Pair<>(guideClass, blocks)); + } + + static { +// registerGuide(SkipGuide.class, AbstractSignBlock.class, SkullBlock.class, BannerBlock.class); + + registerGuide(RotatingBlockGuide.class, AbstractSkullBlock.class, AbstractSignBlock.class, AbstractBannerBlock.class); + registerGuide(SlabGuide.class, SlabBlock.class); + registerGuide(TorchGuide.class, TorchBlock.class); + registerGuide(FarmlandGuide.class, FarmlandBlock.class); + registerGuide(TillingGuide.class, FarmlandBlock.class); + registerGuide(RailGuesserGuide.class, AbstractRailBlock.class); + registerGuide(ChestGuide.class, ChestBlock.class); + registerGuide(FlowerPotGuide.class, FlowerPotBlock.class); + registerGuide(FlowerPotFillGuide.class, FlowerPotBlock.class); + + registerGuide(PropertySpecificGuesserGuide.class, + RepeaterBlock.class, ComparatorBlock.class, RedstoneWireBlock.class, RedstoneTorchBlock.class, + BambooBlock.class, CactusBlock.class, SaplingBlock.class, ScaffoldingBlock.class, PointedDripstoneBlock.class, + HorizontalConnectingBlock.class, DoorBlock.class, TrapdoorBlock.class, FenceGateBlock.class, ChestBlock.class, + SnowBlock.class, SeaPickleBlock.class, CandleBlock.class, LeverBlock.class, EndPortalFrameBlock.class, + NoteBlock.class, CampfireBlock.class, PoweredRailBlock.class, LeavesBlock.class, TripwireHookBlock.class); + registerGuide(FallingBlockGuide.class, FallingBlock.class); + registerGuide(BlockIndifferentGuesserGuide.class, BambooBlock.class, BigDripleafStemBlock.class, BigDripleafBlock.class, + TwistingVinesPlantBlock.class, TripwireBlock.class); + + registerGuide(CampfireExtinguishGuide.class, CampfireBlock.class); + registerGuide(LightCandleGuide.class, AbstractCandleBlock.class); + registerGuide(EnderEyeGuide.class, EndPortalFrameBlock.class); + registerGuide(CycleStateGuide.class, + DoorBlock.class, FenceGateBlock.class, TrapdoorBlock.class, + LeverBlock.class, + RepeaterBlock.class, ComparatorBlock.class, NoteBlock.class); + registerGuide(BlockReplacementGuide.class, SnowBlock.class, SeaPickleBlock.class, CandleBlock.class, SlabBlock.class); + registerGuide(LogGuide.class); + registerGuide(LogStrippingGuide.class); + registerGuide(GuesserGuide.class); + } + + public ArrayList, Class[]>> getGuides() { + return guides; + } + + public Guide[] getInteractionGuides(SchematicBlockState state) { + ArrayList, Class[]>> guides = getGuides(); + + ArrayList applicableGuides = new ArrayList<>(); + for (Pair, Class[]> guidePair : guides) { + try { + if (guidePair.getRight().length == 0) { + applicableGuides.add(guidePair.getLeft().getConstructor(SchematicBlockState.class).newInstance(state)); + continue; + } + + for (Class clazz : guidePair.getRight()) { + if (clazz.isInstance(state.targetState.getBlock())) { + applicableGuides.add(guidePair.getLeft().getConstructor(SchematicBlockState.class).newInstance(state)); + } + } + } catch (Exception ignored) { + } + } + + return applicableGuides.toArray(Guide[]::new); + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/SkipGuide.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/SkipGuide.java new file mode 100644 index 000000000..e7133cebc --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/SkipGuide.java @@ -0,0 +1,37 @@ +package me.aleksilassila.litematica.printer.v1_20.guides; + +import me.aleksilassila.litematica.printer.v1_20.SchematicBlockState; +import me.aleksilassila.litematica.printer.v1_20.actions.Action; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.item.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class SkipGuide extends Guide { + public SkipGuide(SchematicBlockState state) { + super(state); + } + + @Override + public boolean skipOtherGuides() { + return true; + } + + @Override + public boolean canExecute(ClientPlayerEntity player) { + return false; + } + + @Override + public @NotNull List execute(ClientPlayerEntity player) { + return new ArrayList<>(); + } + + @Override + protected @NotNull List getRequiredItems() { + return Collections.singletonList(ItemStack.EMPTY); + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/interaction/CampfireExtinguishGuide.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/interaction/CampfireExtinguishGuide.java new file mode 100644 index 000000000..7c366d111 --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/interaction/CampfireExtinguishGuide.java @@ -0,0 +1,34 @@ +package me.aleksilassila.litematica.printer.v1_20.guides.interaction; + +import me.aleksilassila.litematica.printer.v1_20.SchematicBlockState; +import net.minecraft.block.CampfireBlock; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.item.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.List; + +public class CampfireExtinguishGuide extends InteractionGuide { + boolean shouldBeLit; + boolean isLit; + + public CampfireExtinguishGuide(SchematicBlockState state) { + super(state); + + shouldBeLit = getProperty(targetState, CampfireBlock.LIT).orElse(false); + isLit = getProperty(currentState, CampfireBlock.LIT).orElse(false); + } + + @Override + public boolean canExecute(ClientPlayerEntity player) { + if (!super.canExecute(player)) return false; + + return (currentState.getBlock() instanceof CampfireBlock) && !shouldBeLit && isLit; + } + + @Override + protected @NotNull List getRequiredItems() { + return Arrays.stream(SHOVEL_ITEMS).map(ItemStack::new).toList(); + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/interaction/CycleStateGuide.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/interaction/CycleStateGuide.java new file mode 100644 index 000000000..9563dc66a --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/interaction/CycleStateGuide.java @@ -0,0 +1,45 @@ +package me.aleksilassila.litematica.printer.v1_20.guides.interaction; + +import me.aleksilassila.litematica.printer.v1_20.SchematicBlockState; +import net.minecraft.block.BlockState; +import net.minecraft.block.LeverBlock; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.state.property.Properties; +import net.minecraft.state.property.Property; +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; +import java.util.List; + +public class CycleStateGuide extends InteractionGuide { + private static final Property[] propertiesToIgnore = new Property[]{ + Properties.POWERED, + Properties.LIT + }; + + public CycleStateGuide(SchematicBlockState state) { + super(state); + } + + @Override + public boolean canExecute(ClientPlayerEntity player) { + if (!super.canExecute(player)) return false; + + return targetState.getBlock() == currentState.getBlock(); + } + + @Override + protected @NotNull List getRequiredItems() { + return Collections.singletonList(ItemStack.EMPTY); + } + + @Override + protected boolean statesEqual(BlockState state1, BlockState state2) { + if (state2.getBlock() instanceof LeverBlock) { + return super.statesEqual(state1, state2); + } + + return statesEqualIgnoreProperties(state1, state2, propertiesToIgnore); + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/interaction/EnderEyeGuide.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/interaction/EnderEyeGuide.java new file mode 100644 index 000000000..720b112f5 --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/interaction/EnderEyeGuide.java @@ -0,0 +1,33 @@ +package me.aleksilassila.litematica.printer.v1_20.guides.interaction; + +import me.aleksilassila.litematica.printer.v1_20.SchematicBlockState; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.state.property.Properties; +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; +import java.util.List; + +public class EnderEyeGuide extends InteractionGuide { + public EnderEyeGuide(SchematicBlockState state) { + super(state); + } + + @Override + public boolean canExecute(ClientPlayerEntity player) { + if (!super.canExecute(player)) return false; + + if (currentState.contains(Properties.EYE) && targetState.contains(Properties.EYE)) { + return !currentState.get(Properties.EYE) && targetState.get(Properties.EYE); + } + + return false; + } + + @Override + protected @NotNull List getRequiredItems() { + return Collections.singletonList(new ItemStack(Items.ENDER_EYE)); + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/interaction/FlowerPotFillGuide.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/interaction/FlowerPotFillGuide.java new file mode 100644 index 000000000..43e29d26c --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/interaction/FlowerPotFillGuide.java @@ -0,0 +1,40 @@ +package me.aleksilassila.litematica.printer.v1_20.guides.interaction; + +import me.aleksilassila.litematica.printer.v1_20.SchematicBlockState; +import net.minecraft.block.Block; +import net.minecraft.block.FlowerPotBlock; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.item.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; +import java.util.List; + +public class FlowerPotFillGuide extends InteractionGuide { + private final Block content; + + public FlowerPotFillGuide(SchematicBlockState state) { + super(state); + + Block targetBlock = state.targetState.getBlock(); + if (targetBlock instanceof FlowerPotBlock) { + this.content = ((FlowerPotBlock) targetBlock).getContent(); + } else { + this.content = null; + } + } + + @Override + public boolean canExecute(ClientPlayerEntity player) { + if (content == null) return false; + if (!(currentState.getBlock() instanceof FlowerPotBlock)) return false; + + return super.canExecute(player); + } + + @Override + protected @NotNull List getRequiredItems() { + if (content == null) return Collections.emptyList(); + else return Collections.singletonList(new ItemStack(content)); + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/interaction/InteractionGuide.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/interaction/InteractionGuide.java new file mode 100644 index 000000000..d41a120c7 --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/interaction/InteractionGuide.java @@ -0,0 +1,49 @@ +package me.aleksilassila.litematica.printer.v1_20.guides.interaction; + +import me.aleksilassila.litematica.printer.v1_20.implementation.PrinterPlacementContext; +import me.aleksilassila.litematica.printer.v1_20.SchematicBlockState; +import me.aleksilassila.litematica.printer.v1_20.actions.Action; +import me.aleksilassila.litematica.printer.v1_20.actions.PrepareAction; +import me.aleksilassila.litematica.printer.v1_20.actions.ReleaseShiftAction; +import me.aleksilassila.litematica.printer.v1_20.guides.Guide; +import me.aleksilassila.litematica.printer.v1_20.implementation.actions.InteractActionImpl; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3d; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +/** + * A guide that clicks the current block to change its state. + */ +public abstract class InteractionGuide extends Guide { + public InteractionGuide(SchematicBlockState state) { + super(state); + } + + @Override + public @NotNull List execute(ClientPlayerEntity player) { + List actions = new ArrayList<>(); + + BlockHitResult hitResult = new BlockHitResult(Vec3d.ofCenter(state.blockPos), Direction.UP, state.blockPos, false); + ItemStack requiredItem = getRequiredItem(player).orElse(ItemStack.EMPTY); + int requiredSlot = getRequiredItemStackSlot(player); + + if (requiredSlot == -1) return actions; + + PrinterPlacementContext ctx = new PrinterPlacementContext(player, hitResult, requiredItem, requiredSlot); + + actions.add(new ReleaseShiftAction()); + actions.add(new PrepareAction(ctx)); + actions.add(new InteractActionImpl(ctx)); + + return actions; + } + + @Override + abstract protected @NotNull List getRequiredItems(); +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/interaction/LightCandleGuide.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/interaction/LightCandleGuide.java new file mode 100644 index 000000000..b0f352e0b --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/interaction/LightCandleGuide.java @@ -0,0 +1,36 @@ +package me.aleksilassila.litematica.printer.v1_20.guides.interaction; + +import me.aleksilassila.litematica.printer.v1_20.SchematicBlockState; +import net.minecraft.block.AbstractCandleBlock; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.state.property.Properties; +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; +import java.util.List; + +public class LightCandleGuide extends InteractionGuide { + boolean shouldBeLit; + boolean isLit; + + public LightCandleGuide(SchematicBlockState state) { + super(state); + + shouldBeLit = getProperty(targetState, Properties.LIT).orElse(false); + isLit = getProperty(currentState, Properties.LIT).orElse(false); + } + + @Override + protected @NotNull List getRequiredItems() { + return Collections.singletonList(new ItemStack(Items.FLINT_AND_STEEL)); + } + + @Override + public boolean canExecute(ClientPlayerEntity player) { + if (!super.canExecute(player)) return false; + + return (currentState.getBlock() instanceof AbstractCandleBlock) && shouldBeLit && !isLit; + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/interaction/LogStrippingGuide.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/interaction/LogStrippingGuide.java new file mode 100644 index 000000000..2e6e474ae --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/interaction/LogStrippingGuide.java @@ -0,0 +1,47 @@ +package me.aleksilassila.litematica.printer.v1_20.guides.interaction; + +import me.aleksilassila.litematica.printer.v1_20.LitematicaMixinMod; +import me.aleksilassila.litematica.printer.v1_20.SchematicBlockState; +import me.aleksilassila.litematica.printer.v1_20.mixin.AxeItemAccessor; +import net.minecraft.block.Block; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +public class LogStrippingGuide extends InteractionGuide { + static final Item[] AXE_ITEMS = new Item[]{ + Items.NETHERITE_AXE, + Items.DIAMOND_AXE, + Items.GOLDEN_AXE, + Items.IRON_AXE, + Items.STONE_AXE, + Items.WOODEN_AXE + }; + + public static final Map STRIPPED_BLOCKS = AxeItemAccessor.getStrippedBlocks(); + + public LogStrippingGuide(SchematicBlockState state) { + super(state); + } + + @Override + public boolean canExecute(ClientPlayerEntity player) { + if (!LitematicaMixinMod.STRIP_LOGS.getBooleanValue()) return false; + + if (!super.canExecute(player)) return false; + + Block strippingResult = STRIPPED_BLOCKS.get(currentState.getBlock()); + return strippingResult == targetState.getBlock(); + } + + @Override + protected @NotNull List getRequiredItems() { + return Arrays.stream(AXE_ITEMS).map(ItemStack::new).toList(); + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/interaction/TillingGuide.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/interaction/TillingGuide.java new file mode 100644 index 000000000..e24a4efa1 --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/interaction/TillingGuide.java @@ -0,0 +1,39 @@ +package me.aleksilassila.litematica.printer.v1_20.guides.interaction; + +import me.aleksilassila.litematica.printer.v1_20.SchematicBlockState; +import me.aleksilassila.litematica.printer.v1_20.guides.placement.FarmlandGuide; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.List; + +public class TillingGuide extends InteractionGuide { + public static final Item[] HOE_ITEMS = new Item[]{ + Items.NETHERITE_HOE, + Items.DIAMOND_HOE, + Items.GOLDEN_HOE, + Items.IRON_HOE, + Items.STONE_HOE, + Items.WOODEN_HOE + }; + + public TillingGuide(SchematicBlockState state) { + super(state); + } + + @Override + public boolean canExecute(ClientPlayerEntity player) { + if (!super.canExecute(player)) return false; + + return Arrays.stream(FarmlandGuide.TILLABLE_BLOCKS).anyMatch(b -> b == currentState.getBlock()); + } + + @Override + protected @NotNull List getRequiredItems() { + return Arrays.stream(HOE_ITEMS).map(ItemStack::new).toList(); + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/BlockIndifferentGuesserGuide.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/BlockIndifferentGuesserGuide.java new file mode 100644 index 000000000..8dd53b2b0 --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/BlockIndifferentGuesserGuide.java @@ -0,0 +1,42 @@ +package me.aleksilassila.litematica.printer.v1_20.guides.placement; + +import me.aleksilassila.litematica.printer.v1_20.SchematicBlockState; +import net.minecraft.block.*; + +public class BlockIndifferentGuesserGuide extends GuesserGuide { + public BlockIndifferentGuesserGuide(SchematicBlockState state) { + super(state); + } + + @Override + protected boolean statesEqual(BlockState resultState, BlockState targetState) { + Block targetBlock = targetState.getBlock(); + Block resultBlock = resultState.getBlock(); + + if (targetBlock instanceof BambooBlock) { + return resultBlock instanceof BambooBlock || resultBlock instanceof BambooSaplingBlock; + } + + if (targetBlock instanceof BigDripleafStemBlock) { + if (resultBlock instanceof BigDripleafBlock || resultBlock instanceof BigDripleafStemBlock) { + return resultState.get(HorizontalFacingBlock.FACING) == targetState.get(HorizontalFacingBlock.FACING); + } + } + + if (targetBlock instanceof TwistingVinesPlantBlock) { + if (resultBlock instanceof TwistingVinesBlock) { + return true; + } else if (resultBlock instanceof TwistingVinesPlantBlock) { + return statesEqualIgnoreProperties(resultState, targetState, TwistingVinesBlock.AGE); + } + } + + if (targetBlock instanceof TripwireBlock && resultBlock instanceof TripwireBlock) { + return statesEqualIgnoreProperties(resultState, targetState, + TripwireBlock.ATTACHED, TripwireBlock.DISARMED, TripwireBlock.POWERED, TripwireBlock.NORTH, + TripwireBlock.EAST, TripwireBlock.SOUTH, TripwireBlock.WEST); + } + + return super.statesEqual(resultState, targetState); + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/BlockReplacementGuide.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/BlockReplacementGuide.java new file mode 100644 index 000000000..3d26bc277 --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/BlockReplacementGuide.java @@ -0,0 +1,76 @@ +package me.aleksilassila.litematica.printer.v1_20.guides.placement; + +import me.aleksilassila.litematica.printer.v1_20.implementation.PrinterPlacementContext; +import me.aleksilassila.litematica.printer.v1_20.SchematicBlockState; +import me.aleksilassila.litematica.printer.v1_20.guides.Guide; +import net.minecraft.block.CandleBlock; +import net.minecraft.block.SeaPickleBlock; +import net.minecraft.block.SlabBlock; +import net.minecraft.block.SnowBlock; +import net.minecraft.block.enums.SlabType; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.state.property.IntProperty; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3d; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Optional; + +public class BlockReplacementGuide extends PlacementGuide { + private static final HashMap increasingProperties = new HashMap<>(); + + static { + increasingProperties.put(SnowBlock.LAYERS, null); + increasingProperties.put(SeaPickleBlock.PICKLES, null); + increasingProperties.put(CandleBlock.CANDLES, null); + } + + private Integer currentLevel = null; + private Integer targetLevel = null; + private IntProperty increasingProperty = null; + + public BlockReplacementGuide(SchematicBlockState state) { + super(state); + + for (IntProperty property : increasingProperties.keySet()) { + if (targetState.contains(property) && currentState.contains(property)) { + currentLevel = currentState.get(property); + targetLevel = targetState.get(property); + increasingProperty = property; + break; + } + } + } + + @Override + protected boolean getUseShift(SchematicBlockState state) { + return false; + } + + @Override + public @Nullable PrinterPlacementContext getPlacementContext(ClientPlayerEntity player) { + Optional requiredItem = getRequiredItem(player); + int slot = getRequiredItemStackSlot(player); + if (requiredItem.isEmpty() || slot == -1) return null; + + BlockHitResult hitResult = new BlockHitResult(Vec3d.ofCenter(state.blockPos), Direction.UP, state.blockPos, false); + return new PrinterPlacementContext(player, hitResult, requiredItem.get(), slot); + } + + @Override + public boolean canExecute(ClientPlayerEntity player) { + if (Guide.getProperty(targetState, SlabBlock.TYPE).orElse(null) == SlabType.DOUBLE && Guide.getProperty(currentState, SlabBlock.TYPE).orElse(SlabType.DOUBLE) != SlabType.DOUBLE) { + return super.canExecute(player); + } + + if (currentLevel == null || targetLevel == null || increasingProperty == null) return false; + if (!statesEqualIgnoreProperties(currentState, targetState, CandleBlock.LIT, increasingProperty)) return false; + if (currentLevel >= targetLevel) return false; + + return super.canExecute(player); + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/ChestGuide.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/ChestGuide.java new file mode 100644 index 000000000..4d30e828b --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/ChestGuide.java @@ -0,0 +1,92 @@ +package me.aleksilassila.litematica.printer.v1_20.guides.placement; + +import me.aleksilassila.litematica.printer.v1_20.SchematicBlockState; +import net.minecraft.block.BlockState; +import net.minecraft.block.ChestBlock; +import net.minecraft.block.enums.ChestType; +import net.minecraft.util.math.Direction; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * Whilst making this guide, I learned that chests are much like humans. + * Some prefer to stay single, and some want to connect with another of its kind. + * Also that reversing chest connection logic is an enormous pain in the ass. I spent way too long on this. + * Thanks for coming to my ted talk + */ +public class ChestGuide extends GeneralPlacementGuide { + public ChestGuide(SchematicBlockState state) { + super(state); + } + + @Override + protected boolean getRequiresExplicitShift() { + return true; + } + + @Override + public boolean skipOtherGuides() { + return true; + } + + @Override + protected Optional getLookDirection() { + return getProperty(targetState, ChestBlock.FACING) + .flatMap(facing -> Optional.of(facing.getOpposite())); + } + + @Override + protected List getPossibleSides() { + ChestType targetType = getProperty(targetState, ChestBlock.CHEST_TYPE).orElse(null); + Direction targetFacing = getProperty(targetState, ChestBlock.FACING).orElse(null); + + List sides = new ArrayList<>(); + + if (targetFacing == null || targetType == null) return sides; + + for (Direction direction : Direction.values()) { + if (targetType == ChestType.SINGLE && !willConnectToSide(state, direction)) { + sides.add(direction); + } else if (wantsToConnectToSide(state, direction) && willConnectToSide(state, direction)) { // :D + sides.add(direction); + } + } + + // Place single chests if cannot connect any existing chests + if (sides.isEmpty()) { + for (Direction direction : Direction.values()) { + if (!wantsToConnectToSide(state, direction) && !willConnectToSide(state, direction)) { + sides.add(direction); + } + } + } + + return sides; + } + + private boolean willConnectToSide(SchematicBlockState state, Direction neighborDirection) { + BlockState neighbor = state.offset(neighborDirection).currentState; + ChestType neighborType = getProperty(neighbor, ChestBlock.CHEST_TYPE).orElse(null); + Direction neighborFacing = getProperty(neighbor, ChestBlock.FACING).orElse(null); + Direction facing = getProperty(state.targetState, ChestBlock.FACING).orElse(null); + + if (neighborType == null || neighborFacing == null || facing == null) return false; + + if (facing.getAxis() == neighborDirection.getAxis() || neighborDirection.getAxis() == Direction.Axis.Y) + return false; + + return neighborType == ChestType.SINGLE && neighborFacing == facing && state.targetState.getBlock() == neighbor.getBlock(); + } + + private boolean wantsToConnectToSide(SchematicBlockState state, Direction direction) { + ChestType type = getProperty(state.targetState, ChestBlock.CHEST_TYPE).orElse(null); + Direction facing = getProperty(state.targetState, ChestBlock.FACING).orElse(null); + if (type == null || facing == null || type == ChestType.SINGLE) return false; + + Direction neighborDirection = type == ChestType.LEFT ? facing.rotateYClockwise() : facing.rotateYCounterclockwise(); + + return direction == neighborDirection; + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/FallingBlockGuide.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/FallingBlockGuide.java new file mode 100644 index 000000000..b12c38b91 --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/FallingBlockGuide.java @@ -0,0 +1,37 @@ +package me.aleksilassila.litematica.printer.v1_20.guides.placement; + +import me.aleksilassila.litematica.printer.v1_20.SchematicBlockState; +import net.minecraft.block.BlockState; +import net.minecraft.block.FallingBlock; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.util.math.Direction; + +public class FallingBlockGuide extends GuesserGuide { + + public FallingBlockGuide(SchematicBlockState state) { + super(state); + } + + boolean blockPlacement() { + if (targetState.getBlock() instanceof FallingBlock) { + BlockState below = state.world.getBlockState(state.blockPos.offset(Direction.DOWN)); + return FallingBlock.canFallThrough(below); + } + + return false; + } + + @Override + public boolean canExecute(ClientPlayerEntity player) { + if (blockPlacement()) return false; + + return super.canExecute(player); + } + + @Override + public boolean skipOtherGuides() { + if (blockPlacement()) return true; + + return super.skipOtherGuides(); + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/FarmlandGuide.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/FarmlandGuide.java new file mode 100644 index 000000000..b611a4f25 --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/FarmlandGuide.java @@ -0,0 +1,29 @@ +package me.aleksilassila.litematica.printer.v1_20.guides.placement; + +import me.aleksilassila.litematica.printer.v1_20.SchematicBlockState; +import net.minecraft.block.Block; +import net.minecraft.block.Blocks; +import net.minecraft.item.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.List; + +public class FarmlandGuide extends GeneralPlacementGuide { + public static final Block[] TILLABLE_BLOCKS = new Block[]{ + Blocks.DIRT, + Blocks.GRASS_BLOCK, + Blocks.COARSE_DIRT, + Blocks.ROOTED_DIRT, + Blocks.DIRT_PATH, + }; + + public FarmlandGuide(SchematicBlockState state) { + super(state); + } + + @Override + protected @NotNull List getRequiredItems() { + return Arrays.stream(TILLABLE_BLOCKS).map(b -> getBlockItem(b.getDefaultState())).toList(); + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/FlowerPotGuide.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/FlowerPotGuide.java new file mode 100644 index 000000000..f01f7e850 --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/FlowerPotGuide.java @@ -0,0 +1,20 @@ +package me.aleksilassila.litematica.printer.v1_20.guides.placement; + +import me.aleksilassila.litematica.printer.v1_20.SchematicBlockState; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; +import java.util.List; + +public class FlowerPotGuide extends GeneralPlacementGuide { + public FlowerPotGuide(SchematicBlockState state) { + super(state); + } + + @Override + protected @NotNull List getRequiredItems() { + return Collections.singletonList(new ItemStack(Items.FLOWER_POT)); + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/GeneralPlacementGuide.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/GeneralPlacementGuide.java new file mode 100644 index 000000000..1fb2e2677 --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/GeneralPlacementGuide.java @@ -0,0 +1,119 @@ +package me.aleksilassila.litematica.printer.v1_20.guides.placement; + +import me.aleksilassila.litematica.printer.v1_20.implementation.PrinterPlacementContext; +import me.aleksilassila.litematica.printer.v1_20.SchematicBlockState; +import net.minecraft.block.SlabBlock; +import net.minecraft.block.enums.SlabType; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3d; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +/** + * An old school guide where there are defined specific conditions + * for player state depending on the block being placed. + */ +public class GeneralPlacementGuide extends PlacementGuide { + public GeneralPlacementGuide(SchematicBlockState state) { + super(state); + } + + protected List getPossibleSides() { + return Arrays.asList(Direction.values()); + } + + protected Optional getLookDirection() { + return Optional.empty(); + } + + protected boolean getRequiresSupport() { + return false; + } + + protected boolean getRequiresExplicitShift() { + return false; + } + + protected Vec3d getHitModifier(Direction validSide) { + return new Vec3d(0, 0, 0); + } + + private Optional getValidSide(SchematicBlockState state) { + boolean printInAir = false; // LitematicaMixinMod.PRINT_IN_AIR.getBooleanValue(); + + List sides = getPossibleSides(); + + if (sides.isEmpty()) { + return Optional.empty(); + } + + List validSides = new ArrayList<>(); + for (Direction side : sides) { + if (printInAir && !getRequiresSupport()) { + return Optional.of(side); + } else { + SchematicBlockState neighborState = state.offset(side); + + if (getProperty(neighborState.currentState, SlabBlock.TYPE).orElse(null) == SlabType.DOUBLE) { + validSides.add(side); + continue; + } + + if (canBeClicked(neighborState.world, neighborState.blockPos) && // Handle unclickable grass for example + !neighborState.currentState.isReplaceable()) + validSides.add(side); + } + } + + for (Direction validSide : validSides) { + if (!isInteractive(state.offset(validSide).currentState.getBlock())) { + return Optional.of(validSide); + } + } + + return validSides.isEmpty() ? Optional.empty() : Optional.of(validSides.get(0)); + } + + protected boolean getUseShift(SchematicBlockState state) { + if (getRequiresExplicitShift()) return true; + + Direction clickSide = getValidSide(state).orElse(null); + if (clickSide == null) return false; + return isInteractive(state.offset(clickSide).currentState.getBlock()); + } + + private Optional getHitVector(SchematicBlockState state) { + return getValidSide(state).map(side -> Vec3d.ofCenter(state.blockPos) + .add(Vec3d.of(side.getVector()).multiply(0.5)) + .add(getHitModifier(side))); + } + + @Nullable + public PrinterPlacementContext getPlacementContext(ClientPlayerEntity player) { + try { + Optional validSide = getValidSide(state); + Optional hitVec = getHitVector(state); + Optional requiredItem = getRequiredItem(player); + int requiredSlot = getRequiredItemStackSlot(player); + + if (validSide.isEmpty() || hitVec.isEmpty() || requiredItem.isEmpty() || requiredSlot == -1) return null; + + Optional lookDirection = getLookDirection(); + boolean requiresShift = getUseShift(state); + + BlockHitResult blockHitResult = new BlockHitResult(hitVec.get(), validSide.get().getOpposite(), state.blockPos.offset(validSide.get()), false); + + return new PrinterPlacementContext(player, blockHitResult, requiredItem.get(), requiredSlot, lookDirection.orElse(null), requiresShift); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/GuesserGuide.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/GuesserGuide.java new file mode 100644 index 000000000..6a7b9d03d --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/GuesserGuide.java @@ -0,0 +1,110 @@ +package me.aleksilassila.litematica.printer.v1_20.guides.placement; + +import me.aleksilassila.litematica.printer.v1_20.LitematicaMixinMod; +import me.aleksilassila.litematica.printer.v1_20.implementation.PrinterPlacementContext; +import me.aleksilassila.litematica.printer.v1_20.SchematicBlockState; +import net.minecraft.block.BlockState; +import net.minecraft.block.ChestBlock; +import net.minecraft.block.SlabBlock; +import net.minecraft.block.enums.ChestType; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3d; +import org.jetbrains.annotations.Nullable; + +/** + * This is the placement guide that most blocks will use. + * It will try to predict the correct player state for producing the right blockState + * by brute forcing the correct hit vector and look direction. + */ +public class GuesserGuide extends GeneralPlacementGuide { + private PrinterPlacementContext contextCache = null; + + protected static Direction[] directionsToTry = new Direction[]{ + Direction.NORTH, + Direction.SOUTH, + Direction.EAST, + Direction.WEST, + Direction.UP, + Direction.DOWN + }; + protected static Vec3d[] hitVecsToTry = new Vec3d[]{ + new Vec3d(-0.25, -0.25, -0.25), + new Vec3d(+0.25, -0.25, -0.25), + new Vec3d(-0.25, +0.25, -0.25), + new Vec3d(-0.25, -0.25, +0.25), + new Vec3d(+0.25, +0.25, -0.25), + new Vec3d(-0.25, +0.25, +0.25), + new Vec3d(+0.25, -0.25, +0.25), + new Vec3d(+0.25, +0.25, +0.25), + }; + + public GuesserGuide(SchematicBlockState state) { + super(state); + } + + @Nullable + @Override + public PrinterPlacementContext getPlacementContext(ClientPlayerEntity player) { + if (contextCache != null && !LitematicaMixinMod.DEBUG) return contextCache; + + ItemStack requiredItem = getRequiredItem(player).orElse(ItemStack.EMPTY); + int slot = getRequiredItemStackSlot(player); + + if (slot == -1) return null; + + for (Direction lookDirection : directionsToTry) { + for (Direction side : directionsToTry) { + BlockPos neighborPos = state.blockPos.offset(side); + BlockState neighborState = state.world.getBlockState(neighborPos); + boolean requiresShift = getRequiresExplicitShift() || isInteractive(neighborState.getBlock()); + + if (!canBeClicked(state.world, neighborPos) || // Handle unclickable grass for example + neighborState.isReplaceable()) + continue; + + Vec3d hitVec = Vec3d.ofCenter(state.blockPos) + .add(Vec3d.of(side.getVector()).multiply(0.5)); + + for (Vec3d hitVecToTry : hitVecsToTry) { + Vec3d multiplier = Vec3d.of(side.getVector()); + multiplier = new Vec3d(multiplier.x == 0 ? 1 : 0, multiplier.y == 0 ? 1 : 0, multiplier.z == 0 ? 1 : 0); + + BlockHitResult hitResult = new BlockHitResult(hitVec.add(hitVecToTry.multiply(multiplier)), side.getOpposite(), neighborPos, false); + PrinterPlacementContext context = new PrinterPlacementContext(player, hitResult, requiredItem, slot, lookDirection, requiresShift); + BlockState result = getRequiredItemAsBlock(player) + .orElse(targetState.getBlock()) + .getPlacementState(context); // FIXME torch shift clicks another torch and getPlacementState is the clicked block, which is true + + if (result != null && (statesEqual(result, targetState) || correctChestPlacement(targetState, result))) { + contextCache = context; + return context; + } + } + } + } + + return null; + } + + @Override + public boolean canExecute(ClientPlayerEntity player) { + if (targetState.getBlock() instanceof SlabBlock) return false; // Slabs are a special case + + return super.canExecute(player); + } + + private boolean correctChestPlacement(BlockState targetState, BlockState result) { + if (targetState.contains(ChestBlock.CHEST_TYPE) && result.contains(ChestBlock.CHEST_TYPE) && result.get(ChestBlock.FACING) == targetState.get(ChestBlock.FACING)) { + ChestType targetChestType = targetState.get(ChestBlock.CHEST_TYPE); + ChestType resultChestType = result.get(ChestBlock.CHEST_TYPE); + + return targetChestType != ChestType.SINGLE && resultChestType == ChestType.SINGLE; + } + + return false; + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/LogGuide.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/LogGuide.java new file mode 100644 index 000000000..0316eb4e1 --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/LogGuide.java @@ -0,0 +1,54 @@ +package me.aleksilassila.litematica.printer.v1_20.guides.placement; + +import me.aleksilassila.litematica.printer.v1_20.LitematicaMixinMod; +import me.aleksilassila.litematica.printer.v1_20.SchematicBlockState; +import me.aleksilassila.litematica.printer.v1_20.guides.interaction.LogStrippingGuide; +import net.minecraft.block.Block; +import net.minecraft.block.PillarBlock; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.Direction; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class LogGuide extends GeneralPlacementGuide { + public LogGuide(SchematicBlockState state) { + super(state); + } + + @Override + protected List getPossibleSides() { + if (targetState.contains(PillarBlock.AXIS)) { + Direction.Axis axis = targetState.get(PillarBlock.AXIS); + return Arrays.stream(Direction.values()).filter(d -> d.getAxis() == axis).toList(); + } + + return new ArrayList<>(); + } + + @Override + protected @NotNull List getRequiredItems() { + for (Block log : LogStrippingGuide.STRIPPED_BLOCKS.keySet()) { + if (targetState.getBlock() == LogStrippingGuide.STRIPPED_BLOCKS.get(log)) { + return Collections.singletonList(new ItemStack(log)); + } + } + + return super.getRequiredItems(); + } + + @Override + public boolean canExecute(ClientPlayerEntity player) { + if (!LitematicaMixinMod.STRIP_LOGS.getBooleanValue()) return false; + + if (LogStrippingGuide.STRIPPED_BLOCKS.containsValue(targetState.getBlock())) { + return super.canExecute(player); + } + + return false; + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/PlacementGuide.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/PlacementGuide.java new file mode 100644 index 000000000..2a9133dad --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/PlacementGuide.java @@ -0,0 +1,139 @@ +package me.aleksilassila.litematica.printer.v1_20.guides.placement; + +import me.aleksilassila.litematica.printer.v1_20.LitematicaMixinMod; +import me.aleksilassila.litematica.printer.v1_20.implementation.PrinterPlacementContext; +import me.aleksilassila.litematica.printer.v1_20.SchematicBlockState; +import me.aleksilassila.litematica.printer.v1_20.actions.Action; +import me.aleksilassila.litematica.printer.v1_20.actions.PrepareAction; +import me.aleksilassila.litematica.printer.v1_20.actions.ReleaseShiftAction; +import me.aleksilassila.litematica.printer.v1_20.guides.Guide; +import me.aleksilassila.litematica.printer.v1_20.implementation.actions.InteractActionImpl; +import net.minecraft.block.*; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.item.BlockItem; +import net.minecraft.item.ItemPlacementContext; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.util.shape.VoxelShapes; +import net.minecraft.world.World; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +/** + * Guide that clicks its neighbors to create a placement in target position. + */ +abstract public class PlacementGuide extends Guide { + public PlacementGuide(SchematicBlockState state) { + super(state); + } + + protected ItemStack getBlockItem(BlockState state) { + return state.getBlock().getPickStack(this.state.world, this.state.blockPos, state); + } + + protected Optional getRequiredItemAsBlock(ClientPlayerEntity player) { + Optional requiredItem = getRequiredItem(player); + + if (requiredItem.isEmpty()) { + return Optional.empty(); + } else { + ItemStack itemStack = requiredItem.get(); + + if (itemStack.getItem() instanceof BlockItem) + return Optional.of(((BlockItem) itemStack.getItem()).getBlock()); + else return Optional.empty(); + } + } + + @Override + protected @NotNull List getRequiredItems() { + return Collections.singletonList(getBlockItem(state.targetState)); + } + + abstract protected boolean getUseShift(SchematicBlockState state); + + @Nullable + abstract public PrinterPlacementContext getPlacementContext(ClientPlayerEntity player); + + @Override + public boolean canExecute(ClientPlayerEntity player) { + if (!super.canExecute(player)) return false; + + List requiredItems = getRequiredItems(); + if (requiredItems.isEmpty() || requiredItems.stream().allMatch(i -> i.isOf(Items.AIR))) + return false; + + ItemPlacementContext ctx = getPlacementContext(player); + if (ctx == null || !ctx.canPlace()) return false; +// if (!state.currentState.getMaterial().isReplaceable()) return false; + if (!LitematicaMixinMod.REPLACE_FLUIDS_SOURCE_BLOCKS.getBooleanValue() + && getProperty(state.currentState, FluidBlock.LEVEL).orElse(1) == 0) + return false; + + BlockState resultState = getRequiredItemAsBlock(player) + .orElse(targetState.getBlock()) + .getPlacementState(ctx); + + if (resultState != null) { + if (!resultState.canPlaceAt(state.world, state.blockPos)) return false; + return !(currentState.getBlock() instanceof FluidBlock) || canPlaceInWater(resultState); + } else { + return false; + } + } + + @Override + public @NotNull List execute(ClientPlayerEntity player) { + List actions = new ArrayList<>(); + PrinterPlacementContext ctx = getPlacementContext(player); + + if (ctx == null) return actions; + actions.add(new PrepareAction(ctx)); + actions.add(new InteractActionImpl(ctx)); + if (ctx.shouldSneak) actions.add(new ReleaseShiftAction()); + + return actions; + } + + protected static boolean canBeClicked(World world, BlockPos pos) { + return getOutlineShape(world, pos) != VoxelShapes.empty() && !(world.getBlockState(pos).getBlock() instanceof AbstractSignBlock); // FIXME signs + } + + private static VoxelShape getOutlineShape(World world, BlockPos pos) { + return world.getBlockState(pos).getOutlineShape(world, pos); + } + + public boolean isInteractive(Block block) { + for (Class clazz : interactiveBlocks) { + if (clazz.isInstance(block)) { + return true; + } + } + + return false; + } + + private boolean canPlaceInWater(BlockState blockState) { + Block block = blockState.getBlock(); + if (block instanceof FluidFillable) { + return true; + } else if (!(block instanceof DoorBlock) && !(blockState.getBlock() instanceof AbstractSignBlock) && !blockState.isOf(Blocks.LADDER) && !blockState.isOf(Blocks.SUGAR_CANE) && !blockState.isOf(Blocks.BUBBLE_COLUMN)) { +// Material material = blockState.getMaterial(); +// if (material != Material.PORTAL && material != Material.STRUCTURE_VOID && material != Material.UNDERWATER_PLANT && material != Material.REPLACEABLE_UNDERWATER_PLANT) { +// return material.blocksMovement(); +// } else { +// return true; +// } + return blockState.blocksMovement(); + } + + return true; + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/PropertySpecificGuesserGuide.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/PropertySpecificGuesserGuide.java new file mode 100644 index 000000000..144d23329 --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/PropertySpecificGuesserGuide.java @@ -0,0 +1,52 @@ +package me.aleksilassila.litematica.printer.v1_20.guides.placement; + +import me.aleksilassila.litematica.printer.v1_20.SchematicBlockState; +import net.minecraft.block.*; +import net.minecraft.state.property.Properties; +import net.minecraft.state.property.Property; + +public class PropertySpecificGuesserGuide extends GuesserGuide { + protected static Property[] ignoredProperties = new Property[]{ + RepeaterBlock.DELAY, + ComparatorBlock.MODE, + RedstoneWireBlock.POWER, + RedstoneWireBlock.WIRE_CONNECTION_EAST, + RedstoneWireBlock.WIRE_CONNECTION_NORTH, + RedstoneWireBlock.WIRE_CONNECTION_SOUTH, + RedstoneWireBlock.WIRE_CONNECTION_WEST, + Properties.POWERED, + Properties.OPEN, + PointedDripstoneBlock.THICKNESS, + ScaffoldingBlock.DISTANCE, + ScaffoldingBlock.BOTTOM, + CactusBlock.AGE, + BambooBlock.AGE, + BambooBlock.LEAVES, + BambooBlock.STAGE, + SaplingBlock.STAGE, + HorizontalConnectingBlock.EAST, + HorizontalConnectingBlock.NORTH, + HorizontalConnectingBlock.SOUTH, + HorizontalConnectingBlock.WEST, + SnowBlock.LAYERS, + SeaPickleBlock.PICKLES, + CandleBlock.CANDLES, + EndPortalFrameBlock.EYE, + Properties.LIT, + LeavesBlock.DISTANCE, + LeavesBlock.PERSISTENT, + Properties.ATTACHED, + Properties.NOTE, + Properties.INSTRUMENT, + + }; + + public PropertySpecificGuesserGuide(SchematicBlockState state) { + super(state); + } + + @Override + protected boolean statesEqual(BlockState resultState, BlockState targetState) { + return statesEqualIgnoreProperties(resultState, targetState, ignoredProperties); + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/RailGuesserGuide.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/RailGuesserGuide.java new file mode 100644 index 000000000..4b8dba3d3 --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/RailGuesserGuide.java @@ -0,0 +1,128 @@ +package me.aleksilassila.litematica.printer.v1_20.guides.placement; + +import me.aleksilassila.litematica.printer.v1_20.SchematicBlockState; +import net.minecraft.block.BlockState; +import net.minecraft.block.enums.RailShape; +import net.minecraft.state.property.Properties; +import net.minecraft.util.math.Direction; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +public class RailGuesserGuide extends GuesserGuide { + static final RailShape[] STRAIGHT_RAIL_SHAPES = new RailShape[]{ + RailShape.NORTH_SOUTH, + RailShape.EAST_WEST + }; + + public RailGuesserGuide(SchematicBlockState state) { + super(state); + } + + @Override + public boolean skipOtherGuides() { + return true; + } + + @Override + protected boolean statesEqual(BlockState resultState, BlockState targetState) { + if (!wouldConnectCorrectly()) return false; +// if (wouldBlockAnotherConnection()) return false; + /*TODO: Fully working rail guesser + * If has a neighbor that: + * - Has not been placed yet + * - OR Has been placed but can change shape + * - AND this placement should connect to only one rail, that is not the neighbor + * Then return false + * */ + + if (getRailShape(resultState).isPresent()) { + if (Arrays.stream(STRAIGHT_RAIL_SHAPES).anyMatch(shape -> shape == getRailShape(resultState).orElse(null))) { + return super.statesEqualIgnoreProperties(resultState, targetState, Properties.RAIL_SHAPE, Properties.STRAIGHT_RAIL_SHAPE, Properties.POWERED); + } + } + + return super.statesEqual(resultState, targetState); + } + + private boolean wouldConnectCorrectly() { + RailShape targetShape = getRailShape(state.targetState).orElse(null); + if (targetShape == null) return false; + + List allowedConnections = getRailDirections(targetShape); + + List possibleConnections = new ArrayList<>(); + for (Direction d : Direction.values()) { + if (d.getAxis().isVertical()) continue; + SchematicBlockState neighbor = state.offset(d); + + if (hasFreeConnections(neighbor)) { + possibleConnections.add(d); + } + } + + if (possibleConnections.size() > 2) return false; + + return allowedConnections.containsAll(possibleConnections); + } + +// private boolean wouldBlockAnotherConnection() { +// List possibleConnections = new ArrayList<>(); +// +// for (Direction d : Direction.values()) { +// if (d.getAxis().isVertical()) continue; +// SchematicBlockState neighbor = state.offset(d); +// +// if (couldConnectWrongly(neighbor)) { +// possibleConnections.add(d); +// } +// } +// +// return possibleConnections.size() > 1; +// } + + private boolean hasFreeConnections(SchematicBlockState state) { + List possibleConnections = getRailDirections(state); + if (possibleConnections.isEmpty()) return false; + + for (Direction d : possibleConnections) { + SchematicBlockState neighbor = state.offset(d); + if (neighbor.currentState.getBlock() != neighbor.currentState.getBlock()) { + return false; + } + } + + return possibleConnections.stream().anyMatch(possibleDirection -> { + SchematicBlockState neighbor = state.offset(possibleDirection); + return !getRailDirections(neighbor).contains(possibleDirection.getOpposite()); + }); + } + + private List getRailDirections(SchematicBlockState state) { + RailShape shape = getRailShape(state.currentState).orElse(null); + if (shape == null) return new ArrayList<>(); + + return getRailDirections(shape); + } + + private List getRailDirections(RailShape railShape) { + String name = railShape.getName(); + + if (railShape.isAscending()) { + Direction d = Direction.valueOf(name.replace("ascending_", "").toUpperCase()); + return Arrays.asList(d, d.getOpposite()); + } else { + Direction d1 = Direction.valueOf(name.split("_")[0].toUpperCase()); + Direction d2 = Direction.valueOf(name.split("_")[1].toUpperCase()); + return Arrays.asList(d1, d2); + } + } + + Optional getRailShape(BlockState state) { + Optional shape = getProperty(state, Properties.RAIL_SHAPE); + if (shape.isEmpty()) return getProperty(state, Properties.STRAIGHT_RAIL_SHAPE); + return shape; + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/RotatingBlockGuide.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/RotatingBlockGuide.java new file mode 100644 index 000000000..c8ced5bdd --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/RotatingBlockGuide.java @@ -0,0 +1,58 @@ +package me.aleksilassila.litematica.printer.v1_20.guides.placement; + +import me.aleksilassila.litematica.printer.v1_20.implementation.PrinterPlacementContext; +import me.aleksilassila.litematica.printer.v1_20.SchematicBlockState; +import me.aleksilassila.litematica.printer.v1_20.actions.Action; +import me.aleksilassila.litematica.printer.v1_20.actions.PrepareAction; +import net.minecraft.block.*; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.state.property.Properties; +import net.minecraft.util.math.Direction; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +public class RotatingBlockGuide extends GeneralPlacementGuide { + public RotatingBlockGuide(SchematicBlockState state) { + super(state); + } + + @Override + protected List getPossibleSides() { + Block block = state.targetState.getBlock(); + if (block instanceof WallSkullBlock || block instanceof WallSignBlock || block instanceof WallBannerBlock) { + Optional side = getProperty(state.targetState, Properties.HORIZONTAL_FACING).map(Direction::getOpposite); + return side.map(Collections::singletonList).orElseGet(Collections::emptyList); + } + + return Collections.singletonList(Direction.DOWN); + } + + @Override + public boolean skipOtherGuides() { + return true; + } + + @Override + public @NotNull List execute(ClientPlayerEntity player) { + PrinterPlacementContext ctx = getPlacementContext(player); + + if (ctx == null) return new ArrayList<>(); + + int rotation = getProperty(state.targetState, Properties.ROTATION).orElse(0); + if (targetState.getBlock() instanceof BannerBlock || targetState.getBlock() instanceof SignBlock) { + rotation = (rotation + 8) % 16; + } + + int distTo0 = rotation > 8 ? 16 - rotation : rotation; + float yaw = Math.round(distTo0 / 8f * 180f * (rotation > 8 ? -1 : 1)); + + List actions = super.execute(player); + actions.set(0, new PrepareAction(ctx, yaw, 0)); + + return actions; + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/SlabGuide.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/SlabGuide.java new file mode 100644 index 000000000..449a5c8af --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/SlabGuide.java @@ -0,0 +1,48 @@ +package me.aleksilassila.litematica.printer.v1_20.guides.placement; + +import me.aleksilassila.litematica.printer.v1_20.SchematicBlockState; +import net.minecraft.block.BlockState; +import net.minecraft.block.SlabBlock; +import net.minecraft.block.enums.SlabType; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3d; + +import java.util.Arrays; +import java.util.List; + +public class SlabGuide extends GeneralPlacementGuide { + public SlabGuide(SchematicBlockState state) { + super(state); + } + + @Override + protected List getPossibleSides() { + return Arrays.stream(Direction.values()) + .filter(d -> d != (getRequiredHalf(state).getOpposite()) && + getProperty(state.offset(d).currentState, SlabBlock.TYPE).orElse(SlabType.DOUBLE) == SlabType.DOUBLE) + .toList(); + } + + @Override + protected Vec3d getHitModifier(Direction validSide) { + Direction requiredHalf = getRequiredHalf(state); + if (validSide.getHorizontal() != -1) { + return new Vec3d(0, requiredHalf.getOffsetY() * 0.25, 0); + } else { + return new Vec3d(0, 0, 0); + } + } + + private Direction getRequiredHalf(SchematicBlockState state) { + BlockState targetState = state.targetState; + BlockState currentState = state.currentState; + + if (!currentState.contains(SlabBlock.TYPE)) { + return targetState.get(SlabBlock.TYPE) == SlabType.TOP ? Direction.UP : Direction.DOWN; + } else if (currentState.get(SlabBlock.TYPE) != targetState.get(SlabBlock.TYPE)) { + return currentState.get(SlabBlock.TYPE) == SlabType.TOP ? Direction.DOWN : Direction.UP; + } else { + return Direction.DOWN; + } + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/TorchGuide.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/TorchGuide.java new file mode 100644 index 000000000..099b30de5 --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/guides/placement/TorchGuide.java @@ -0,0 +1,31 @@ +package me.aleksilassila.litematica.printer.v1_20.guides.placement; + +import me.aleksilassila.litematica.printer.v1_20.SchematicBlockState; +import net.minecraft.block.Block; +import net.minecraft.block.HorizontalFacingBlock; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.util.math.Direction; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +public class TorchGuide extends GeneralPlacementGuide { + public TorchGuide(SchematicBlockState state) { + super(state); + } + + @Override + protected List getPossibleSides() { + Optional facing = getProperty(targetState, HorizontalFacingBlock.FACING); + + return facing + .map(direction -> Collections.singletonList(direction.getOpposite())) + .orElseGet(() -> Collections.singletonList(Direction.DOWN)); + } + + @Override + protected Optional getRequiredItemAsBlock(ClientPlayerEntity player) { + return Optional.of(state.targetState.getBlock()); + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/implementation/BlockHelperImpl.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/implementation/BlockHelperImpl.java new file mode 100644 index 000000000..8b8c83a56 --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/implementation/BlockHelperImpl.java @@ -0,0 +1,14 @@ +package me.aleksilassila.litematica.printer.v1_20.implementation; + +import me.aleksilassila.litematica.printer.v1_20.BlockHelper; +import net.minecraft.block.ButtonBlock; + +import java.util.Arrays; + +public class BlockHelperImpl extends BlockHelper { + static { + interactiveBlocks.addAll(Arrays.asList( + ButtonBlock.class + )); + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/implementation/PrinterPlacementContext.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/implementation/PrinterPlacementContext.java new file mode 100644 index 000000000..26fcd96fa --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/implementation/PrinterPlacementContext.java @@ -0,0 +1,59 @@ +package me.aleksilassila.litematica.printer.v1_20.implementation; + +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemPlacementContext; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Hand; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.Direction; +import org.jetbrains.annotations.Nullable; + +public class PrinterPlacementContext extends ItemPlacementContext { + public final @Nullable Direction lookDirection; + public final boolean shouldSneak; + public final BlockHitResult hitResult; + public final int requiredItemSlot; + + public PrinterPlacementContext(PlayerEntity player, BlockHitResult hitResult, ItemStack requiredItem, int requiredItemSlot) { + this(player, hitResult, requiredItem, requiredItemSlot, null, false); + } + + public PrinterPlacementContext(PlayerEntity player, BlockHitResult hitResult, ItemStack requiredItem, int requiredItemSlot, @Nullable Direction lookDirection, boolean requiresSneaking) { + super(player, Hand.MAIN_HAND, requiredItem, hitResult); + + this.lookDirection = lookDirection; + this.shouldSneak = requiresSneaking; + this.hitResult = hitResult; + this.requiredItemSlot = requiredItemSlot; + } + + @Override + public Direction getPlayerLookDirection() { + return lookDirection == null ? super.getPlayerLookDirection() : lookDirection; + } + + @Override + public Direction getVerticalPlayerLookDirection() { + if (lookDirection != null && lookDirection.getOpposite() == super.getVerticalPlayerLookDirection()) + return lookDirection; + return super.getVerticalPlayerLookDirection(); + } + + @Override + public Direction getHorizontalPlayerFacing() { + if (lookDirection == null || !lookDirection.getAxis().isHorizontal()) return super.getHorizontalPlayerFacing(); + + return lookDirection; + } + + @Override + public String toString() { + return "PrinterPlacementContext{" + + "lookDirection=" + lookDirection + + ", requiresSneaking=" + shouldSneak + + ", blockPos=" + hitResult.getBlockPos() + + ", side=" + hitResult.getSide() + +// ", hitVec=" + hitResult + + '}'; + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/implementation/actions/InteractActionImpl.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/implementation/actions/InteractActionImpl.java new file mode 100644 index 000000000..6418f7e62 --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/implementation/actions/InteractActionImpl.java @@ -0,0 +1,20 @@ +package me.aleksilassila.litematica.printer.v1_20.implementation.actions; + +import me.aleksilassila.litematica.printer.v1_20.implementation.PrinterPlacementContext; +import me.aleksilassila.litematica.printer.v1_20.actions.InteractAction; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.util.Hand; +import net.minecraft.util.hit.BlockHitResult; + +public class InteractActionImpl extends InteractAction { + public InteractActionImpl(PrinterPlacementContext context) { + super(context); + } + + @Override + protected void interact(MinecraftClient client, ClientPlayerEntity player, Hand hand, BlockHitResult hitResult) { + client.interactionManager.interactBlock(player, hand, hitResult); + client.interactionManager.interactItem(player, hand); + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/implementation/mixin/MixinClientPlayerEntity.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/implementation/mixin/MixinClientPlayerEntity.java new file mode 100644 index 000000000..90d02a848 --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/implementation/mixin/MixinClientPlayerEntity.java @@ -0,0 +1,102 @@ +package me.aleksilassila.litematica.printer.v1_20.implementation.mixin; + +import com.mojang.authlib.GameProfile; +import fi.dy.masa.litematica.world.SchematicWorldHandler; +import fi.dy.masa.litematica.world.WorldSchematic; +import me.aleksilassila.litematica.printer.v1_20.LitematicaMixinMod; +import me.aleksilassila.litematica.printer.v1_20.Printer; +import me.aleksilassila.litematica.printer.v1_20.SchematicBlockState; +import me.aleksilassila.litematica.printer.v1_20.UpdateChecker; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.SignBlockEntity; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.AbstractClientPlayerEntity; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.network.packet.c2s.play.UpdateSignC2SPacket; +import net.minecraft.text.Text; +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 java.util.Optional; + +@Mixin(ClientPlayerEntity.class) +public class MixinClientPlayerEntity extends AbstractClientPlayerEntity { + + private static boolean didCheckForUpdates = false; + + @Shadow + protected MinecraftClient client; + @Shadow + public ClientPlayNetworkHandler networkHandler; + + public MixinClientPlayerEntity(ClientWorld world, GameProfile profile) { + super(world, profile); + } + + @Inject(at = @At("TAIL"), method = "tick") + public void tick(CallbackInfo ci) { + ClientPlayerEntity clientPlayer = (ClientPlayerEntity) (Object) this; + if (!didCheckForUpdates) { + didCheckForUpdates = true; + + checkForUpdates(); + } + + if (LitematicaMixinMod.printer == null || LitematicaMixinMod.printer.player != clientPlayer) { + System.out.println("Initializing printer, player: " + clientPlayer + ", client: " + client); + LitematicaMixinMod.printer = new Printer(client, clientPlayer); + } + + // Dirty optimization + boolean didFindPlacement = true; + for (int i = 0; i < 10; i++) { + if (didFindPlacement) { + didFindPlacement = LitematicaMixinMod.printer.onGameTick(); + } + LitematicaMixinMod.printer.actionHandler.onGameTick(); + } + } + + public void checkForUpdates() { + new Thread(() -> { + String version = UpdateChecker.version; + String newVersion = UpdateChecker.getPrinterVersion(); + + if (!version.equals(newVersion)) { + client.inGameHud.getChatHud().addMessage(Text.literal("New version of Litematica Printer available in https://github.com/aleksilassila/litematica-printer/releases")); + } + }).start(); + } + + @Inject(method = "openEditSignScreen", at = @At("HEAD"), cancellable = true) + public void openEditSignScreen(SignBlockEntity sign, boolean front, CallbackInfo ci) { + getTargetSignEntity(sign).ifPresent(signBlockEntity -> { + UpdateSignC2SPacket packet = new UpdateSignC2SPacket(sign.getPos(), + front, + signBlockEntity.getText(front).getMessage(0, false).getString(), + signBlockEntity.getText(front).getMessage(1, false).getString(), + signBlockEntity.getText(front).getMessage(2, false).getString(), + signBlockEntity.getText(front).getMessage(3, false).getString()); + this.networkHandler.sendPacket(packet); + ci.cancel(); + }); + } + + private Optional getTargetSignEntity(SignBlockEntity sign) { + WorldSchematic worldSchematic = SchematicWorldHandler.getSchematicWorld(); + SchematicBlockState state = new SchematicBlockState(sign.getWorld(), worldSchematic, sign.getPos()); + + BlockEntity targetBlockEntity = worldSchematic.getBlockEntity(state.blockPos); + + if (targetBlockEntity instanceof SignBlockEntity targetSignEntity) { + return Optional.of(targetSignEntity); + } + + return Optional.empty(); + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/mixin/AxeItemAccessor.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/mixin/AxeItemAccessor.java new file mode 100644 index 000000000..96057ecef --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/mixin/AxeItemAccessor.java @@ -0,0 +1,20 @@ +package me.aleksilassila.litematica.printer.v1_20.mixin; + +import net.minecraft.block.Block; +import net.minecraft.item.AxeItem; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.Map; + +/** + * This class apparently fixes an issue with Quilt. + */ +@Mixin(AxeItem.class) +public interface AxeItemAccessor { + @Accessor("STRIPPED_BLOCKS") + static Map getStrippedBlocks() { + throw new AssertionError("Untransformed @Accessor"); + } + +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/mixin/ConfigsMixin.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/mixin/ConfigsMixin.java new file mode 100644 index 000000000..2d8a44853 --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/mixin/ConfigsMixin.java @@ -0,0 +1,35 @@ +package me.aleksilassila.litematica.printer.v1_20.mixin; + +import com.google.common.collect.ImmutableList; +import fi.dy.masa.litematica.config.Configs; +import fi.dy.masa.malilib.config.IConfigBase; +import fi.dy.masa.malilib.config.options.ConfigHotkey; +import me.aleksilassila.litematica.printer.v1_20.LitematicaMixinMod; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import java.util.List; + +@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.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.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.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.getHotkeyList(); + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/mixin/GuiConfigsMixin.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/mixin/GuiConfigsMixin.java new file mode 100644 index 000000000..2a6be8cf6 --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/mixin/GuiConfigsMixin.java @@ -0,0 +1,61 @@ +package me.aleksilassila.litematica.printer.v1_20.mixin; + +import com.google.common.collect.ImmutableList; +import fi.dy.masa.litematica.gui.GuiConfigs; +import fi.dy.masa.malilib.config.IConfigBase; +import fi.dy.masa.malilib.config.options.ConfigHotkey; +import me.aleksilassila.litematica.printer.v1_20.LitematicaMixinMod; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import java.util.List; + +@Mixin(value = GuiConfigs.class, remap = false) +public class GuiConfigsMixin { + + /*@Overwrite + public List getConfigs() + { + List configs; + ConfigGuiTab tab = DataManager.getConfigGuiTab(); + + if (tab == ConfigGuiTab.GENERIC) + { + configs = LitematicaMixinMod.betterConfigList; + } + else if (tab == ConfigGuiTab.INFO_OVERLAYS) + { + configs = Configs.InfoOverlays.OPTIONS; + } + else if (tab == ConfigGuiTab.VISUALS) + { + configs = Configs.Visuals.OPTIONS; + } + else if (tab == ConfigGuiTab.COLORS) + { + configs = Configs.Colors.OPTIONS; + } + else if (tab == ConfigGuiTab.HOTKEYS) + { + configs = LitematicaMixinMod.betterHotkeyList; + } + else + { + return Collections.emptyList(); + } + + return ConfigOptionWrapper.createFor(configs); + }*/ + + + @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.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.getHotkeyList(); + } +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/mixin/InputHandlerMixin.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/mixin/InputHandlerMixin.java new file mode 100644 index 000000000..6ae175969 --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/mixin/InputHandlerMixin.java @@ -0,0 +1,25 @@ +package me.aleksilassila.litematica.printer.v1_20.mixin; + +import fi.dy.masa.litematica.event.InputHandler; +import fi.dy.masa.malilib.config.options.ConfigHotkey; +import me.aleksilassila.litematica.printer.v1_20.LitematicaMixinMod; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import java.util.List; + +@Mixin(value = InputHandler.class, remap = false) +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.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.getHotkeyList(); + } + +} diff --git a/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/mixin/PlayerMoveC2SPacketMixin.java b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/mixin/PlayerMoveC2SPacketMixin.java new file mode 100644 index 000000000..38326667a --- /dev/null +++ b/v1_20/src/main/java/me/aleksilassila/litematica/printer/v1_20/mixin/PlayerMoveC2SPacketMixin.java @@ -0,0 +1,36 @@ +package me.aleksilassila.litematica.printer.v1_20.mixin; + +import me.aleksilassila.litematica.printer.v1_20.LitematicaMixinMod; +import me.aleksilassila.litematica.printer.v1_20.Printer; +import me.aleksilassila.litematica.printer.v1_20.actions.PrepareAction; +import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyVariable; + +@Mixin(PlayerMoveC2SPacket.class) +public class PlayerMoveC2SPacketMixin { + @ModifyVariable(method = "(DDDFFZZZ)V", at = @At("HEAD"), ordinal = 0) + private static float modifyLookYaw(float yaw) { + Printer printer = LitematicaMixinMod.printer; + if (printer == null) return yaw; + + PrepareAction action = printer.actionHandler.lookAction; + if (action != null && action.modifyYaw) { + if (LitematicaMixinMod.DEBUG) System.out.println("YAW: " + action.yaw); + return action.yaw; + } else return yaw; + } + + @ModifyVariable(method = "(DDDFFZZZ)V", at = @At("HEAD"), ordinal = 1) + private static float modifyLookPitch(float pitch) { + Printer printer = LitematicaMixinMod.printer; + if (printer == null) return pitch; + + PrepareAction action = printer.actionHandler.lookAction; + if (action != null && action.modifyPitch) { + if (LitematicaMixinMod.DEBUG) System.out.println("PITCH: " + action.pitch); + return action.pitch; + } else return pitch; + } +} diff --git a/v1_20/src/main/resources/assets/modid/icon.png b/v1_20/src/main/resources/assets/modid/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..047b91f2347de5cf95f23284476fddbe21ba23fe GIT binary patch literal 453 zcmV;$0XqJPP)QAFYGys`80vegN0XDFh0OXKz&i8?Le#x7{1X)R+00000NkvXXu0mjf73i~T literal 0 HcmV?d00001 diff --git a/v1_20/src/main/resources/fabric.mod.json b/v1_20/src/main/resources/fabric.mod.json new file mode 100644 index 000000000..84e383ca3 --- /dev/null +++ b/v1_20/src/main/resources/fabric.mod.json @@ -0,0 +1,38 @@ +{ + "schemaVersion": 1, + "id": "litematica_printer", + "version": "${version}", + "name": "Litematica Printer", + "description": "A fork of Litematica that adds the missing printer functionality", + "authors": [ + "aleksilassila" + ], + "contact": { + "homepage": "https://github.com/aleksilassila/litematica-printer", + "sources": "https://github.com/aleksilassila/litematica-printer" + }, + "license": "CC0-1.0", + "icon": "assets/modid/icon.png", + "environment": "*", + "entrypoints": { + "main": [ + "me.aleksilassila.litematica.printer.v1_20.LitematicaMixinMod" + ] + }, + "mixins": [ + "litematica-printer.mixins.json", + "litematica-printer-implementation.mixins.json" + ], + "depends": { + "fabricloader": ">=0.14.7", + "fabric": "*", + "minecraft": "1.20.x", + "java": ">=17" + }, + "custom": { + "modmenu": { + "parent": "carpet" + } + } +} + diff --git a/v1_20/src/main/resources/litematica-printer-implementation.mixins.json b/v1_20/src/main/resources/litematica-printer-implementation.mixins.json new file mode 100644 index 000000000..46d79fe99 --- /dev/null +++ b/v1_20/src/main/resources/litematica-printer-implementation.mixins.json @@ -0,0 +1,14 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "me.aleksilassila.litematica.printer.v1_20.implementation.mixin", + "compatibilityLevel": "JAVA_16", + "mixins": [ + ], + "client": [ + "MixinClientPlayerEntity" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/v1_20/src/main/resources/litematica-printer.mixins.json b/v1_20/src/main/resources/litematica-printer.mixins.json new file mode 100644 index 000000000..bb8d1f41a --- /dev/null +++ b/v1_20/src/main/resources/litematica-printer.mixins.json @@ -0,0 +1,18 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "me.aleksilassila.litematica.printer.v1_20.mixin", + "compatibilityLevel": "JAVA_16", + "mixins": [ + ], + "client": [ + "ConfigsMixin", + "GuiConfigsMixin", + "InputHandlerMixin", + "PlayerMoveC2SPacketMixin", + "AxeItemAccessor" + ], + "injectors": { + "defaultRequire": 1 + } +}