From 020b265327926665e35698cc939b994fb8971ca3 Mon Sep 17 00:00:00 2001 From: Jesus <69329388+ejeebus@users.noreply.github.com> Date: Sat, 6 Jun 2026 05:25:12 -0400 Subject: [PATCH 01/14] Add 1.21.11 support --- build.gradle.kts | 4 +- gradle.properties | 1 + gradle/wrapper/gradle-wrapper.properties | 3 +- settings.gradle | 3 + v1_21_11/build.gradle.kts | 68 +++ v1_21_11/gradle.properties | 14 + v1_21_11/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 61608 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 + v1_21_11/gradlew | 244 ++++++++++ v1_21_11/gradlew.bat | 92 ++++ .../printer/v1_21_4/ActionHandler.java | 93 ++++ .../printer/v1_21_4/BlockHelper.java | 31 ++ .../litematica/printer/v1_21_4/FreeLook.java | 109 +++++ .../printer/v1_21_4/InventoryManager.java | 383 ++++++++++++++++ .../printer/v1_21_4/LitematicaMixinMod.java | 63 +++ .../printer/v1_21_4/MovementHandler.java | 212 +++++++++ .../litematica/printer/v1_21_4/Printer.java | 200 ++++++++ .../printer/v1_21_4/SchematicBlockState.java | 42 ++ .../printer/v1_21_4/SchematicConverter.java | 23 + .../printer/v1_21_4/UpdateChecker.java | 43 ++ .../printer/v1_21_4/actions/Action.java | 8 + .../printer/v1_21_4/actions/ActionChain.java | 35 ++ .../v1_21_4/actions/InteractAction.java | 39 ++ .../printer/v1_21_4/actions/PostAction.java | 30 ++ .../v1_21_4/actions/PrepareAction.java | 120 +++++ .../printer/v1_21_4/actions/PrepareLook.java | 116 +++++ .../printer/v1_21_4/actions/PresShift.java | 17 + .../v1_21_4/actions/ReleaseShiftAction.java | 20 + .../config/FreeLookKeyCallbackToggle.java | 26 ++ .../printer/v1_21_4/config/PrinterConfig.java | 185 ++++++++ .../v1_21_4/config/PrinterInputHandler.java | 25 + .../config/PrinterPickBlockKeyCallback.java | 14 + .../printer/v1_21_4/guides/Guide.java | 147 ++++++ .../printer/v1_21_4/guides/Guides.java | 81 ++++ .../printer/v1_21_4/guides/SkipGuide.java | 37 ++ .../interaction/CampfireExtinguishGuide.java | 34 ++ .../guides/interaction/CycleStateGuide.java | 59 +++ .../guides/interaction/EnderEyeGuide.java | 33 ++ .../interaction/FlowerPotFillGuide.java | 40 ++ .../guides/interaction/InteractionGuide.java | 54 +++ .../guides/interaction/LightCandleGuide.java | 36 ++ .../guides/interaction/LogStrippingGuide.java | 47 ++ .../guides/interaction/TillingGuide.java | 39 ++ .../guides/interaction/WaterLogGuide.java | 138 ++++++ .../BlockIndifferentGuesserGuide.java | 54 +++ .../placement/BlockReplacementGuide.java | 75 +++ .../v1_21_4/guides/placement/ChestGuide.java | 92 ++++ .../guides/placement/FacingBlockGuide.java | 51 +++ .../guides/placement/FallingBlockGuide.java | 37 ++ .../guides/placement/FarmlandGuide.java | 29 ++ .../guides/placement/FlowerPotGuide.java | 20 + .../placement/GeneralPlacementGuide.java | 433 ++++++++++++++++++ .../v1_21_4/guides/placement/LogGuide.java | 54 +++ .../guides/placement/PlacementGuide.java | 193 ++++++++ .../PropertySpecificGuesserGuide.java | 75 +++ .../guides/placement/RailGuesserGuide.java | 128 ++++++ .../guides/placement/RotatingBlockGuide.java | 68 +++ .../v1_21_4/guides/placement/SlabGuide.java | 37 ++ .../v1_21_4/guides/placement/TorchGuide.java | 34 ++ .../implementation/BlockHelperImpl.java | 14 + .../PrinterPlacementContext.java | 92 ++++ .../actions/AirPlaceAction.java | 66 +++ .../actions/InteractActionImpl.java | 26 ++ .../actions/UseItemActionImpl.java | 31 ++ .../mixin/MixinClientPlayerEntity.java | 109 +++++ .../v1_21_4/mixin/AbstractBlockInvoker.java | 21 + .../v1_21_4/mixin/AxeItemAccessor.java | 20 + .../v1_21_4/mixin/ButtonListenerMixin.java | 41 ++ .../printer/v1_21_4/mixin/ConfigsMixin.java | 43 ++ .../v1_21_4/mixin/GuiConfigsMixin.java | 61 +++ .../v1_21_4/mixin/InputHandlerMixin.java | 25 + .../mixin/LitematicaSchematicAccessor.java | 20 + .../MixinAccessorClientPlayerEntity.java | 27 ++ .../mixin/MixinAccessorKeyBinding.java | 12 + .../printer/v1_21_4/mixin/MixinCamera.java | 22 + .../v1_21_4/mixin/MixinClientConnection.java | 62 +++ .../mixin/MixinClientPlayerEntity.java | 19 + .../printer/v1_21_4/mixin/MixinMouse.java | 57 +++ .../mixin/PlayerMoveC2SPacketMixin.java | 36 ++ .../src/main/resources/assets/modid/icon.png | Bin 0 -> 25538 bytes v1_21_11/src/main/resources/fabric.mod.json | 38 ++ ...ematica-printer-implementation.mixins.json | 14 + .../resources/litematica-printer.mixins.json | 26 ++ 83 files changed, 5272 insertions(+), 3 deletions(-) create mode 100644 v1_21_11/build.gradle.kts create mode 100644 v1_21_11/gradle.properties create mode 100644 v1_21_11/gradle/wrapper/gradle-wrapper.jar create mode 100644 v1_21_11/gradle/wrapper/gradle-wrapper.properties create mode 100644 v1_21_11/gradlew create mode 100644 v1_21_11/gradlew.bat create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/ActionHandler.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/BlockHelper.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/FreeLook.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/InventoryManager.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/LitematicaMixinMod.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/MovementHandler.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/Printer.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/SchematicBlockState.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/SchematicConverter.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/UpdateChecker.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/Action.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/ActionChain.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/InteractAction.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/PostAction.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/PrepareAction.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/PrepareLook.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/PresShift.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/ReleaseShiftAction.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/config/FreeLookKeyCallbackToggle.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/config/PrinterConfig.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/config/PrinterInputHandler.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/config/PrinterPickBlockKeyCallback.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/Guide.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/Guides.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/SkipGuide.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/CampfireExtinguishGuide.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/CycleStateGuide.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/EnderEyeGuide.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/FlowerPotFillGuide.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/InteractionGuide.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/LightCandleGuide.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/LogStrippingGuide.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/TillingGuide.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/WaterLogGuide.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/BlockIndifferentGuesserGuide.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/BlockReplacementGuide.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/ChestGuide.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/FacingBlockGuide.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/FallingBlockGuide.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/FarmlandGuide.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/FlowerPotGuide.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/GeneralPlacementGuide.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/LogGuide.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/PlacementGuide.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/PropertySpecificGuesserGuide.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/RailGuesserGuide.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/RotatingBlockGuide.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/SlabGuide.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/TorchGuide.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/implementation/BlockHelperImpl.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/implementation/PrinterPlacementContext.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/implementation/actions/AirPlaceAction.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/implementation/actions/InteractActionImpl.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/implementation/actions/UseItemActionImpl.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/implementation/mixin/MixinClientPlayerEntity.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/AbstractBlockInvoker.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/AxeItemAccessor.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/ButtonListenerMixin.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/ConfigsMixin.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/GuiConfigsMixin.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/InputHandlerMixin.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/LitematicaSchematicAccessor.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinAccessorClientPlayerEntity.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinAccessorKeyBinding.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinCamera.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinClientConnection.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinClientPlayerEntity.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinMouse.java create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/PlayerMoveC2SPacketMixin.java create mode 100644 v1_21_11/src/main/resources/assets/modid/icon.png create mode 100644 v1_21_11/src/main/resources/fabric.mod.json create mode 100644 v1_21_11/src/main/resources/litematica-printer-implementation.mixins.json create mode 100644 v1_21_11/src/main/resources/litematica-printer.mixins.json diff --git a/build.gradle.kts b/build.gradle.kts index f35dda499..833c3f5bb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,6 @@ plugins { id("java") - id("fabric-loom").version("1.7-SNAPSHOT").apply(false) + id("fabric-loom").version("1.15-SNAPSHOT").apply(false) } subprojects { @@ -22,6 +22,8 @@ val mod_version: String by project val buildAll = tasks.create("buildAll") { dependsOn(":v1_21_4:build") + dependsOn(":v1_21_11:build") + // ... // This isn't working.... you still have to run each build individually /* tasks.findByName(":v1_19_3:build")?.mustRunAfter(":v1_19_4:build") tasks.findByName(":v1_19:build")?.mustRunAfter(":v1_19_3:build") diff --git a/gradle.properties b/gradle.properties index d12f32c8c..5ce0bdc54 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,3 +5,4 @@ org.gradle.parallel=false mod_version=3.4.0 maven_group=net.fabricmc archives_base_name=litematica-printer +org.gradle.java.home=C:\\Program Files\\Eclipse Adoptium\\jdk-21.0.11.10-hotspot \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2617362fd..9937dae91 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip -networkTimeout=10000 +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle b/settings.gradle index c88d72cf2..73d2f0c47 100644 --- a/settings.gradle +++ b/settings.gradle @@ -9,3 +9,6 @@ pluginManagement { } } include 'v1_21_4' + +include(":v1_21_11") +project(":v1_21_11").projectDir = file("v1_21_11") diff --git a/v1_21_11/build.gradle.kts b/v1_21_11/build.gradle.kts new file mode 100644 index 000000000..bd1679c5d --- /dev/null +++ b/v1_21_11/build.gradle.kts @@ -0,0 +1,68 @@ +import net.fabricmc.loom.task.RemapJarTask + +plugins { + id("maven-publish") +} + +java.sourceCompatibility = JavaVersion.VERSION_21 +java.targetCompatibility = JavaVersion.VERSION_21 + +val archives_base_name: 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 +val minecraft_version: String by project +val yarn_mappings: String by project +val loader_version: String by project +val fabric_version: String by project + +dependencies { +// implementation(project(":common")) + minecraft("com.mojang:minecraft:${minecraft_version}") + mappings("net.fabricmc:yarn:${yarn_mappings}:v2") + annotationProcessor("io.github.llamalad7:mixinextras-fabric:0.4.1")?.let { implementation(it)?.let { include(it) } } + modImplementation("net.fabricmc:fabric-loader:${loader_version}") + modImplementation("net.fabricmc.fabric-api:fabric-api:${fabric_version}") +modImplementation("com.github.sakura-ryoko:malilib:1.21.11-0.27.8") + //Replace masa litematica with sakura-ryoko fork + modImplementation("com.github.sakura-ryoko:litematica:1.21.11-0.26.3") { + exclude(group = "fi.dy.masa.malilib") +} + modImplementation("com.ptsmods:devlogin:3.5") +} + +repositories { + maven("https://masa.dy.fi/maven") + maven("https://www.cursemaven.com") + maven("https://jitpack.io") + maven("https://maven.fallenbreath.me/releases") +} + +// Process resources +tasks.withType { + inputs.property("version", mod_version) + + filesMatching("fabric.mod.json") { + expand(mapOf("version" to mod_version)) + } +} + +tasks.build { + finalizedBy("renameJar") +} + +tasks.create("renameJar") { + val remapJar = tasks.getByName("remapJar") + val jarFile = remapJar.archiveFile.get().asFile + + doLast { + val targetFile = File(jarFile.parent, "$archives_base_name-$mod_version-mc$minecraft_version.jar") + println("Renaming ${jarFile.absolutePath} to ${targetFile.absolutePath}") + targetFile.delete() + jarFile.renameTo(targetFile) + } +} + diff --git a/v1_21_11/gradle.properties b/v1_21_11/gradle.properties new file mode 100644 index 000000000..16b3385dd --- /dev/null +++ b/v1_21_11/gradle.properties @@ -0,0 +1,14 @@ +# 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.4:0.18.1 +# https://www.curseforge.com/minecraft/mc-mods/litematica/files +#litematica_fileid=5170070 +#litematica_projectid=308892 +# Fabric Properties: https://fabricmc.net/develop/ +minecraft_version=1.21.11 +yarn_mappings=1.21.11+build.4 +loader_version=0.18.1 + +# Fabric API +fabric_version=0.139.5+1.21.11 diff --git a/v1_21_11/gradle/wrapper/gradle-wrapper.jar b/v1_21_11/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..ccebba7710deaf9f98673a68957ea02138b60d0a GIT binary patch literal 61608 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 literal 0 HcmV?d00001 diff --git a/v1_21_11/gradle/wrapper/gradle-wrapper.properties b/v1_21_11/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..0d1842103 --- /dev/null +++ b/v1_21_11/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/v1_21_11/gradlew b/v1_21_11/gradlew new file mode 100644 index 000000000..79a61d421 --- /dev/null +++ b/v1_21_11/gradlew @@ -0,0 +1,244 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/v1_21_11/gradlew.bat b/v1_21_11/gradlew.bat new file mode 100644 index 000000000..93e3f59f1 --- /dev/null +++ b/v1_21_11/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/ActionHandler.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/ActionHandler.java new file mode 100644 index 000000000..f172347b8 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/ActionHandler.java @@ -0,0 +1,93 @@ +package me.aleksilassila.litematica.printer.v1_21_4; + +import me.aleksilassila.litematica.printer.v1_21_4.actions.Action; +import me.aleksilassila.litematica.printer.v1_21_4.actions.ActionChain; +import me.aleksilassila.litematica.printer.v1_21_4.actions.PrepareAction; +import me.aleksilassila.litematica.printer.v1_21_4.config.PrinterConfig; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; + +import java.util.LinkedList; +import java.util.Queue; + +public class ActionHandler { + private final MinecraftClient client; + private final ClientPlayerEntity player; + + private final Queue currentTickActions = new LinkedList<>(); + private final Queue previousTickActions = new LinkedList<>(); + public PrepareAction lookAction = null; + + public ActionHandler(MinecraftClient client, ClientPlayerEntity player) { + this.client = client; + this.player = player; + } + + public void processCurrentTickActions() { + Action nextAction = currentTickActions.poll(); + while (nextAction != null) { + if (LitematicaMixinMod.DEBUG) { + System.out.println("Sending action " + nextAction); + } + boolean success = nextAction.send(client, player); + if (PrinterConfig.PRINTER_DEBUG_LOG.getBooleanValue()) { + if (success) { + System.out.println("Action (Pre) success " + nextAction); + } else { + System.out.println("Action (Pre) failed " + nextAction); + } + } + if (!success) { + currentTickActions.clear(); + break; + } + nextAction = currentTickActions.poll(); + } + Printer.inactivityCounter = 0; + } + + public void processPreviousTickActions() { + // Hours spent figuring this out: 6 + Action nextAction = previousTickActions.poll(); + while (nextAction != null) { + if (LitematicaMixinMod.DEBUG) { + System.out.println("Sending action " + nextAction); + } + boolean success = nextAction.send(client, player); + if (PrinterConfig.PRINTER_DEBUG_LOG.getBooleanValue()) { + if (success) { + System.out.println("Action (Post Tick) success " + nextAction); + } else { + System.out.println("Action (Post Tick) failed " + nextAction); + } + } + if (!success) { + previousTickActions.clear(); + break; + } + nextAction = previousTickActions.poll(); + } + } + + public boolean acceptsActions() { + return currentTickActions.isEmpty() && previousTickActions.isEmpty(); + } + + public void addActions(Action... actions) { + if (!acceptsActions()) return; + + for (Action action : actions) { + if (action instanceof PrepareAction) + lookAction = (PrepareAction) action; + } + + for (Action action : actions) { + if (action instanceof ActionChain chain) { + currentTickActions.addAll(chain.getActionsCurrentTick()); + previousTickActions.addAll(chain.getActionsNextTick()); + } else { + currentTickActions.add(action); + } + } + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/BlockHelper.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/BlockHelper.java new file mode 100644 index 000000000..536e288fa --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/BlockHelper.java @@ -0,0 +1,31 @@ +package me.aleksilassila.litematica.printer.v1_21_4; + +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_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/FreeLook.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/FreeLook.java new file mode 100644 index 000000000..8359c1632 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/FreeLook.java @@ -0,0 +1,109 @@ +package me.aleksilassila.litematica.printer.v1_21_4; + +import fi.dy.masa.malilib.config.options.ConfigBoolean; +import me.aleksilassila.litematica.printer.v1_21_4.config.PrinterConfig; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.option.Perspective; + +public class FreeLook { + static FreeLook INSTANCE = null; + float cameraYaw = 0; + float cameraPitch = 0; + Perspective prevPerspective = Perspective.FIRST_PERSON; + MinecraftClient mc = MinecraftClient.getInstance(); + boolean enabled = false; + int ticksSinceLastRotation = 0; + +// BooleanSetting changePers = addBooleanSetting("Change Perspective",true); +// EnumSetting cameraMode = addEnumSetting("Camera Mode",Mode.CAMERA); +// FloatSetting sensitivity = addFloatSetting("Sensitivity",8,0,10); + private FreeLook() { + enabled = PrinterConfig.FREE_LOOK.getBooleanValue(); + PrinterConfig.FREE_LOOK.setValueChangeCallback(this::setEnabled); + } + + public static FreeLook getInstance() { + if (INSTANCE == null) { + INSTANCE = new FreeLook(); + } + return INSTANCE; + } + + public void onGameTick() { + if (shouldRotate() && ((ticksSinceLastRotation -1) == PrinterConfig.FREE_LOOK_LOOK_BACK.getIntegerValue() || PrinterConfig.FREE_LOOK_LOOK_BACK_ALWAYS_ROTATE_PLAYER.getBooleanValue())) { + if (mc.player != null) { + // Reset player rotation. The mouse mixin only rotates the player by a delta. So without this the player + // rotation would be offset from the camera rotation. + mc.player.setYaw(cameraYaw); + mc.player.setPitch(cameraPitch); + } + } + ticksSinceLastRotation++; + } + + private void setEnabled(ConfigBoolean configBoolean) { + this.enabled = configBoolean.getBooleanValue(); + if (PrinterConfig.PRINTER_DEBUG_LOG.getBooleanValue()) System.out.println("FreeLook: " + enabled); + if (enabled) { + onEnable(); + } else { + onDisable(); + } + } + + void onEnable() { + if(mc.player == null) { + return; + } + this.enabled = true; + if (PrinterConfig.PRINTER_DEBUG_LOG.getBooleanValue()) System.out.println("FreeLook: onEnable"); + + cameraPitch = mc.player.getPitch(); + cameraYaw = mc.player.getYaw(); + prevPerspective = mc.options.getPerspective(); + + if (PrinterConfig.FREE_LOOK_THIRD_PERSON.getBooleanValue()) { + mc.options.setPerspective(Perspective.THIRD_PERSON_BACK); + } else { + mc.options.setPerspective(Perspective.FIRST_PERSON); + } + } + + void onDisable() { + this.enabled = false; + if (PrinterConfig.PRINTER_DEBUG_LOG.getBooleanValue()) System.out.println("FreeLook: onDisable"); + if (prevPerspective != null && mc.options.getPerspective() != prevPerspective /*&& changePers.getValue()*/) mc.options.setPerspective(prevPerspective); + } + + public boolean isEnabled() { + return enabled; + } + + public boolean shouldRotate() { + return enabled && PrinterConfig.FREE_LOOK_LOOK_BACK.getIntegerValue() != 0 && ticksSinceLastRotation > PrinterConfig.FREE_LOOK_LOOK_BACK.getIntegerValue(); + } + + public float getCameraYaw() { + return cameraYaw; + } + + public float getCameraPitch() { + return cameraPitch; + } + + public void setCameraYaw(float v) { + cameraYaw = v; + } + + public void setCameraPitch(float v) { + cameraPitch = v; + } + + public void setPrevPerspective(Perspective perspective) { + prevPerspective = perspective; + } + + public Perspective getPrevPerspective() { + return prevPerspective; + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/InventoryManager.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/InventoryManager.java new file mode 100644 index 000000000..4be0539c7 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/InventoryManager.java @@ -0,0 +1,383 @@ +package me.aleksilassila.litematica.printer.v1_21_4; + +import fi.dy.masa.litematica.config.Configs; +import fi.dy.masa.litematica.materials.MaterialCache; +import fi.dy.masa.litematica.util.InventoryUtils; +import fi.dy.masa.litematica.world.WorldSchematic; +import fi.dy.masa.malilib.config.options.ConfigString; +import me.aleksilassila.litematica.printer.v1_21_4.config.PrinterConfig; +import net.minecraft.block.BlockState; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.network.packet.c2s.play.CloseHandledScreenC2SPacket; +import net.minecraft.network.packet.c2s.play.UpdateSelectedSlotC2SPacket; +import net.minecraft.screen.PlayerScreenHandler; +import net.minecraft.screen.slot.SlotActionType; +import net.minecraft.util.Hand; +import net.minecraft.util.collection.DefaultedList; +import net.minecraft.util.math.BlockPos; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; + +public class InventoryManager { + private static final List USABLE_SLOTS = new ArrayList<>(); + int delay = 0; + /** + * The queue of items to pull from the inventory + */ + private final ArrayList hotbarSlots = new ArrayList<>(9); + + private final MinecraftClient mc = MinecraftClient.getInstance(); + private final Deque rollingSlots = new ArrayDeque<>(); + private final Deque lastUsedSlots = new ArrayDeque<>(); + private static InventoryManager instance; + + private InventoryManager() { + // Probably add a way to configure this + for (int i = 0; i < 9; i++) { + rollingSlots.add(i); + lastUsedSlots.add(i); + hotbarSlots.add(new SlotInfo()); + } + } + + public static void setHotbarSlots(ConfigString config) { + USABLE_SLOTS.clear(); + String configStr = config.getStringValue(); + String[] parts = configStr.split(","); + + for (String str : parts) { + try { + int slotNum = Integer.parseInt(str) - 1; + + if (PlayerInventory.isValidHotbarIndex(slotNum) && + !USABLE_SLOTS.contains(slotNum)) { + USABLE_SLOTS.add(slotNum); + } + } catch (NumberFormatException ignore) { + } + } + } + + public static InventoryManager getInstance() { + if (instance == null) { + instance = new InventoryManager(); + } + return instance; + } + + public void reset() { + hotbarSlots.forEach((info) -> info.ticksLocked = 0); + } + + public boolean tick() { + if (mc.player == null || mc.interactionManager == null) { + return false; + } + + delay = Math.max(0, delay - 1); + + return false; + } + + /** + * Swaps the item from the inventory to the hotbar + * + * @param item The item to pull from the inventory + * @return True if the item could the swapped into the hotbar + */ + private boolean swapToHotbar(ClientPlayerEntity player, Item item) { + if (getHotbarSlotWithItem(player, new ItemStack(item)) != -1) { // Already in the hotbar dummy + return true; + } + int slot = getBestInventorySlotWithItem(player, new ItemStack(item)); + if (slot == -1) { + return false; + } + int nextSlot = nextHotbarSlot(); + if (nextSlot == -1) { + return false; + } + hotbarSlots.get(nextSlot).addTicksLocked(10); + hotbarSlots.get(nextSlot).waitingForItem = item; + player.getInventory().selectedSlot = nextSlot; + if (PrinterConfig.PRINTER_DEBUG_LOG.getBooleanValue()) { + System.out.println("Swapping item from inventory: " + slot + " into hotbar -> " + nextSlot); + } + swapAway(slot, nextSlot, player.playerScreenHandler); + delay += PrinterConfig.INVENTORY_DELAY.getIntegerValue(); + return true; + } + + public void pickSlot(WorldSchematic world, ClientPlayerEntity player, BlockPos pos) { + if (mc.interactionManager == null) { + return; + } + PlayerInventory inv = player.getInventory(); + BlockState state = world.getBlockState(pos); + ItemStack stack = MaterialCache.getInstance().getRequiredBuildItemForState(state, world, pos); + int slot = inv.getSlotWithStack(stack); + boolean shouldPick = slot > 8; + if (slot != -1 && !shouldPick) { + player.getInventory().selectedSlot = slot; + } else if (slot != -1) { + InventoryUtils.setPickedItemToHand(slot, stack, mc); // https://github.com/sakura-ryoko/litematica-printer/blob/f8e38a2b31708e61f8a5fad0f2989d6834495da4/src/main/java/me/aleksilassila/litematica/printer/actions/PrepareAction.java#L71 + } else if (Configs.Generic.PICK_BLOCK_SHULKERS.getBooleanValue()) { + slot = findSlotWithBoxWithItem(player.getInventory(), stack, true); + if (slot > -1) { + if (slot > 8) { + InventoryUtils.setPickedItemToHand(slot, stack, mc); + } else { + inv.selectedSlot = slot; + } + } + } + } + + private void depositCursorStack() { + mc.getNetworkHandler().sendPacket(new CloseHandledScreenC2SPacket(mc.player.currentScreenHandler.syncId)); +// if (!mc.player.playerScreenHandler.getCursorStack().isEmpty()) { +// mc.player.getInventory().offerOrDrop(mc.player.playerScreenHandler.getCursorStack()); +// mc.player.playerScreenHandler.setCursorStack(ItemStack.EMPTY); +// } + } + + public static int findSlotWithBoxWithItem(PlayerInventory inventory, ItemStack stackReference, boolean lestFirst) { + int bestCount = lestFirst ? Integer.MAX_VALUE : 0; + int bestSlot = -1; + + for (int slotNum = 0; slotNum < inventory.main.size(); slotNum += 1) { + ItemStack itemStack = inventory.getStack(slotNum); + int count = shulkerBoxItemCount(itemStack, stackReference); + if (lestFirst && count < bestCount && count > 0) { + bestCount = count; + bestSlot = slotNum; + } else if (!lestFirst && count > bestCount) { + bestCount = count; + bestSlot = slotNum; + } + } + + return bestSlot; + } + + public static int shulkerBoxItemCount(ItemStack stack, ItemStack referenceItem) { + DefaultedList items = fi.dy.masa.malilib.util.InventoryUtils.getStoredItems(stack); + int count = 0; + if (!items.isEmpty()) { + for (ItemStack item : items) { + if (fi.dy.masa.malilib.util.InventoryUtils.areStacksEqual(item, referenceItem)) { + count += item.getCount(); + } + } + } + + return count; + } + + public boolean select(ItemStack itemStack) { + ClientPlayerEntity player = mc.player; + if (player == null || mc.interactionManager == null) { + return false; + } + + // Swap logic start + if (itemStack != null) { + PlayerInventory inventory = player.getInventory(); + + // This thing is straight from MinecraftClient#doItemPick() + if (player.getAbilities().creativeMode) { + depositCursorStack(); + this.addPickBlock(inventory, itemStack); + mc.interactionManager.clickCreativeStack(player.getStackInHand(Hand.MAIN_HAND), 36 + inventory.selectedSlot); + updateLastUsedSlot(inventory.selectedSlot); + return true; + } else { + int hotbarSlot = getHotbarSlotWithItem(player, itemStack); + if (hotbarSlot == -1) { + // Start equipping from inventory + if (delay > 0) { + return false; + } + depositCursorStack(); + if (swapToHotbar(mc.player, itemStack.getItem())) { + // If true the item should now be somewhere in the hotbar + hotbarSlot = getHotbarSlotWithItem(player, itemStack); + if (hotbarSlot == -1) { + return false; + } + if (hotbarSlot != player.getInventory().selectedSlot) { + player.networkHandler.sendPacket(new UpdateSelectedSlotC2SPacket(hotbarSlot)); + player.getInventory().selectedSlot = hotbarSlot; + } + updateLastUsedSlot(hotbarSlot); + return false; + } + return false; + } else { + depositCursorStack(); + // Switch to hotbar slot + if (hotbarSlot != player.getInventory().selectedSlot) { + player.networkHandler.sendPacket(new UpdateSelectedSlotC2SPacket(hotbarSlot)); + player.getInventory().selectedSlot = hotbarSlot; + } + updateLastUsedSlot(hotbarSlot); + return true; + } + } + } + return false; + } + + // https://github.com/sakura-ryoko/litematica-printer/blob/f8e38a2b31708e61f8a5fad0f2989d6834495da4/src/main/java/me/aleksilassila/litematica/printer/actions/PrepareAction.java#L95 + private void addPickBlock(PlayerInventory inv, ItemStack stack) { + int slot = inv.getSlotWithStack(stack); + + if (slot >= 0 && slot < 9) { + inv.selectedSlot = slot; + } else { + if (slot == -1) { + inv.selectedSlot = inv.getSwappableHotbarSlot(); + + if (!inv.main.get(inv.selectedSlot).isEmpty()) { + int empty = inv.getEmptySlot(); + + if (empty != -1) { + inv.main.set(empty, inv.main.get(inv.selectedSlot)); + } + } + inv.main.set(inv.selectedSlot, stack); + } else { + inv.swapSlotWithHotbar(slot); + } + } + } + + public boolean swapAway(int inventorySlot, int hotbarSlot, PlayerScreenHandler screenHandler) { + ClientPlayNetworkHandler networkHandler = mc.getNetworkHandler(); + if (mc.player == null || mc.interactionManager == null || networkHandler == null) return false; + + mc.interactionManager.clickSlot(screenHandler.syncId, inventorySlot, hotbarSlot, SlotActionType.SWAP, mc.player); + return true; + } + + /** + * Returns the next available slot for a new item. Returns the slot to the end of the queue after returning. + * Range from 0-8 + * + * @return The next available slot + */ + private int nextHotbarSlot() { + final String mode = PrinterConfig.PRINTER_INVENTORY_MANAGEMENT_MODE.getStringValue(); + + if (PrinterConfig.InventoryManagementModeEnum.LEAST_USED.is(mode)) { + if (!lastUsedSlots.isEmpty()) { + List usableSlots = lastUsedSlots.stream().filter(USABLE_SLOTS::contains).toList(); + if (usableSlots.isEmpty()) { + return -1; + } + int last = usableSlots.getLast(); + lastUsedSlots.remove(last); + lastUsedSlots.addFirst(last); + if (PrinterConfig.PRINTER_DEBUG_LOG.getBooleanValue()) { + System.out.println("Next new least used slot: " + last + "Last used slots: " + lastUsedSlots); + } + return last; + } + } else if (PrinterConfig.InventoryManagementModeEnum.ROLLING.is(mode)) { + if (!rollingSlots.isEmpty()) { + List usableSlots = rollingSlots.stream().filter(USABLE_SLOTS::contains).toList(); + if (usableSlots.isEmpty()) { + return -1; + } + int slot = usableSlots.getLast(); + rollingSlots.remove(slot); + rollingSlots.addLast(slot); + if (PrinterConfig.PRINTER_DEBUG_LOG.getBooleanValue()) { + System.out.println("Next new rolling slot: " + slot + "Rolling slots: " + rollingSlots); + } + return slot; + } + } + if (PrinterConfig.PRINTER_DEBUG_LOG.getBooleanValue()) { + System.out.println("No slots available"); + } + return -1; + } + + /** + * Returns the first slot with the given item. Returns -1 if no slot is found. + * + * @param player The player to check + * @param itemStack The item to check for + * @return The first slot with the given item + */ + private static int getBestInventorySlotWithItem(ClientPlayerEntity player, ItemStack itemStack) { + PlayerInventory inventory = player.getInventory(); + + if (itemStack.isEmpty()) return -1; + + int lowestCount = 0; + int lowestSlot = -1; + for (int i = 9; i < inventory.main.size(); ++i) { + if (!(inventory.main.get(i)).isEmpty() && ItemStack.areItemsAndComponentsEqual(itemStack, inventory.main.get(i))) { + if (inventory.main.get(i).getCount() < lowestCount || lowestSlot == -1) { + lowestCount = inventory.main.get(i).getCount(); + lowestSlot = i; + } + } + } + + return lowestSlot; + } + + /** + * Return a number between 0-8 representing the hotbar slot with the given item. + */ + public int getHotbarSlotWithItem(ClientPlayerEntity player, ItemStack itemStack) { + PlayerInventory inventory = player.getInventory(); + + if (itemStack.isEmpty()) return -1; + + for (int i = 0; i < 9; ++i) { + if (!inventory.main.get(i).isEmpty() && ItemStack.areItemsEqual(inventory.main.get(i), itemStack)) { +// if (hotbarSlots.get(i).ticksLocked == 0) { + return i; +// } + } + } + + return -1; + } + + private void updateLastUsedSlot(int slot) { + lastUsedSlots.remove(slot); + lastUsedSlots.addFirst(slot); + if (PrinterConfig.PRINTER_DEBUG_LOG.getBooleanValue()) { + String list = lastUsedSlots.stream().map(String::valueOf).reduce((a, b) -> a + ", " + b).orElse(""); + System.out.println("Updating last used slot: " + slot + ". Now: " + list); + } + } + + static class SlotInfo { + private int ticksLocked = 0; + @Nullable + public Item waitingForItem = null; + + public void addTicksLocked(int ticks) { + ticksLocked += ticks; + } + + public void decrementTicksLocked() { + ticksLocked--; + } + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/LitematicaMixinMod.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/LitematicaMixinMod.java new file mode 100644 index 000000000..18f6e0244 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/LitematicaMixinMod.java @@ -0,0 +1,63 @@ +package me.aleksilassila.litematica.printer.v1_21_4; + +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 me.aleksilassila.litematica.printer.v1_21_4.config.PrinterConfig; +import net.fabricmc.api.ModInitializer; + +import java.util.List; + +public class LitematicaMixinMod implements ModInitializer { + + public static Printer printer; + public static PrinterConfig printerConfig = PrinterConfig.getInstance(); + public static boolean DEBUG = false; + public static MovementHandler movementHandler = new MovementHandler(); + public static FreeLook freeLook = FreeLook.getInstance(); + // 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); + list.addAll(printerConfig.getOptions()); + + 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)); + PrinterConfig.onInitialize(); + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/MovementHandler.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/MovementHandler.java new file mode 100644 index 000000000..9bc7c3fc1 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/MovementHandler.java @@ -0,0 +1,212 @@ +package me.aleksilassila.litematica.printer.v1_21_4; + +import me.aleksilassila.litematica.printer.v1_21_4.config.PrinterConfig; +import me.aleksilassila.litematica.printer.v1_21_4.mixin.MixinAccessorClientPlayerEntity; +import me.aleksilassila.litematica.printer.v1_21_4.mixin.MixinAccessorKeyBinding; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.ingame.CraftingScreen; +import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.client.option.KeyBinding; +import net.minecraft.client.util.InputUtil; +import net.minecraft.network.packet.c2s.play.PlayerInputC2SPacket; +import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket; +import net.minecraft.util.math.Vec3d; + +public class MovementHandler { + private static final MinecraftClient mc = MinecraftClient.getInstance(); + private boolean disableNextTick = false; + + public void onGameTick() { + if (mc.player == null) return; + + // Disabled check + if (!PrinterConfig.FREE_LOOK.getBooleanValue() || !LitematicaMixinMod.PRINT_MODE.getBooleanValue()) { + if (disableNextTick) { + disableNextTick = false; + InputDirections.apply(InputDirections.getCurrentInput()); + } + return; + } + + // Disable in inventories + if (mc.currentScreen != null) { + if (PrinterConfig.MOVE_WHILE_IN_INVENTORY.getBooleanValue()) { + if (mc.currentScreen instanceof CraftingScreen || mc.currentScreen instanceof CreativeInventoryScreen) { + return; + } + } else { + return; + } + } + + // Get current inputs + InputDirections currentInputDirection = InputDirections.getCurrentInput(); + + float cameraYaw = mc.gameRenderer.getCamera().getYaw() % 360; + if (currentInputDirection != InputDirections.NONE) { + // lastPlayerInputDirection = currentInputDirection; + // Calculate resulting control direction and apply them + float inputYaw = currentInputDirection.getYaw(); + float playerYaw = (mc.player.getYaw() + 360) % 360; + + // Calculate the result yaw + float playerRelativeYaw = inputYaw + playerYaw; + float resultPlayerYaw = playerRelativeYaw - cameraYaw; + InputDirections resultDirection = InputDirections.getDirection(resultPlayerYaw); + if (resultDirection == null || resultDirection == InputDirections.NONE) { + // ChatUtil.sendClientMessage("Result direction is null"); + Printer.logger.warn("Result direction is null / None"); + return; + } + + InputDirections.apply(resultDirection); + } else /*if (overwriteKeys.getValue())*/ { +// Printer.logger.info("No input direction"); + InputDirections.apply(InputDirections.NONE); + } + } + + public static void grimRotate(ClientPlayerEntity player, float yaw, float pitch) { + Vec3d playerPos = player.getPos(); + mc.getNetworkHandler().sendPacket(new PlayerInputC2SPacket(player.input.playerInput)); + mc.getNetworkHandler().sendPacket(new PlayerMoveC2SPacket.Full(playerPos.x, playerPos.y, playerPos.z, yaw, pitch, player.isOnGround(), player.horizontalCollision)); + ((MixinAccessorClientPlayerEntity) mc.player).setLastYaw(yaw); + ((MixinAccessorClientPlayerEntity) mc.player).setLastPitch(pitch); + } + + public void onDisable(ClientPlayerEntity player) { + disableNextTick = true; + } + + enum InputDirections { + FORWARD(0), + FORWARD_LEFT(45), + FORWARD_RIGHT(315), + LEFT(90), + RIGHT(270), + BACK(180), + BACK_LEFT(135), + BACK_RIGHT(225), + NONE(-1); + private final float yaw; + + InputDirections(float i) { + this.yaw = i; + } + + static InputDirections getDirection(float yaw) { + while (yaw < 0) yaw += 360; + yaw = yaw % 360; + if (yaw < 22.5) return FORWARD; + if (yaw < 67.5) return FORWARD_LEFT; + if (yaw < 112.5) return LEFT; + if (yaw < 157.5) return BACK_LEFT; + if (yaw < 202.5) return BACK; + if (yaw < 247.5) return BACK_RIGHT; + if (yaw < 292.5) return RIGHT; + if (yaw < 337.5) return FORWARD_RIGHT; + return FORWARD; + } + + /** + * Returns a unit vector in the direction of the input direction + * + * @return a unit vector in the direction of the input direction + */ + public Vec3d getVec3d() { + return new Vec3d(Math.sin(Math.toRadians(yaw)), 0, Math.cos(Math.toRadians(yaw))); + } + + public float getYaw() { + return yaw; + } + + static InputDirections getCurrentInput() { + if (isKeyPressed(mc.options.forwardKey)) { + if (isKeyPressed(mc.options.leftKey)) { + return FORWARD_LEFT; + } else if (isKeyPressed(mc.options.rightKey)) { + return FORWARD_RIGHT; + } else { + return FORWARD; + } + } else if (isKeyPressed(mc.options.backKey)) { + if (isKeyPressed(mc.options.leftKey)) { + return BACK_LEFT; + } else if (isKeyPressed(mc.options.rightKey)) { + return BACK_RIGHT; + } else { + return BACK; + } + } else if (isKeyPressed(mc.options.leftKey)) { + return LEFT; + } else if (isKeyPressed(mc.options.rightKey)) { + return RIGHT; + } else { + return NONE; + } + } + + public boolean isPressed() { + switch (this) { + case FORWARD -> isKeyPressed(mc.options.forwardKey); + case FORWARD_LEFT -> { + return isKeyPressed(mc.options.forwardKey) && isKeyPressed(mc.options.leftKey); + } + case FORWARD_RIGHT -> { + return isKeyPressed(mc.options.forwardKey) && isKeyPressed(mc.options.rightKey); + } + case LEFT -> isKeyPressed(mc.options.leftKey); + case RIGHT -> isKeyPressed(mc.options.rightKey); + case BACK -> isKeyPressed(mc.options.backKey); + case BACK_LEFT -> { + return isKeyPressed(mc.options.backKey) && isKeyPressed(mc.options.leftKey); + } + case BACK_RIGHT -> { + return isKeyPressed(mc.options.backKey) && isKeyPressed(mc.options.rightKey); + } + default -> { + return false; + } + } + return false; + } + + static void apply(InputDirections direction) { + mc.options.forwardKey.setPressed(false); + mc.options.leftKey.setPressed(false); + mc.options.rightKey.setPressed(false); + mc.options.backKey.setPressed(false); + switch (direction) { + case FORWARD -> mc.options.forwardKey.setPressed(true); + case FORWARD_LEFT -> { + mc.options.leftKey.setPressed(true); + mc.options.forwardKey.setPressed(true); + } + case FORWARD_RIGHT -> { + mc.options.rightKey.setPressed(true); + mc.options.forwardKey.setPressed(true); + } + case LEFT -> mc.options.leftKey.setPressed(true); + case RIGHT -> mc.options.rightKey.setPressed(true); + case BACK -> mc.options.backKey.setPressed(true); + case BACK_LEFT -> { + mc.options.backKey.setPressed(true); + mc.options.leftKey.setPressed(true); + } + case BACK_RIGHT -> { + mc.options.backKey.setPressed(true); + mc.options.rightKey.setPressed(true); + } + case NONE -> { + + } + } + } + } + + static boolean isKeyPressed(KeyBinding keyBinding) { + return InputUtil.isKeyPressed(MinecraftClient.getInstance().getWindow().getHandle(), ((MixinAccessorKeyBinding) keyBinding).getBoundKey().getCode()); + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/Printer.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/Printer.java new file mode 100644 index 000000000..b83faf023 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/Printer.java @@ -0,0 +1,200 @@ +package me.aleksilassila.litematica.printer.v1_21_4; + +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_21_4.actions.Action; +import me.aleksilassila.litematica.printer.v1_21_4.config.PrinterConfig; +import me.aleksilassila.litematica.printer.v1_21_4.guides.Guide; +import me.aleksilassila.litematica.printer.v1_21_4.guides.Guides; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.entity.player.PlayerAbilities; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec2f; +import net.minecraft.util.math.Vec3d; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +public class Printer { + public static final Logger logger = LogManager.getLogger("litematica-printer"); + @NotNull + public final ClientPlayerEntity player; + MinecraftClient mc = MinecraftClient.getInstance(); + + public final ActionHandler actionHandler; + + private final Guides interactionGuides = new Guides(); + public static final InventoryManager inventoryManager = InventoryManager.getInstance(); + public static int inactivityCounter = 0; + static final LinkedList blockPosTimeout = new LinkedList<>(); + int delayCounter = 0; + @Nullable + public static Vec2f lastRotation = null; + + public Printer(@NotNull MinecraftClient client, @NotNull ClientPlayerEntity player) { + this.player = player; + + this.actionHandler = new ActionHandler(client, player); + } + + public void onMiddleClick() { + if (mc.world == null || mc.player == null) return; + BlockPos pos = RayTraceUtils.getSchematicWorldTraceIfClosest(mc.world, mc.player, 6.0); + + if (pos != null) { + WorldSchematic world = SchematicWorldHandler.getSchematicWorld(); + if (world == null) return; + inventoryManager.pickSlot(world, player, pos); + } + } + + public boolean onGameTick() { + WorldSchematic worldSchematic = SchematicWorldHandler.getSchematicWorld(); + blockPosTimeout.forEach((entry) -> entry.timer--); + blockPosTimeout.removeIf((entry) -> entry.timer <= 0); + + // If the inactivityCounter is greater than the inactive snap back value, then set the lastRotation to the current rotation + // This is used to snap back to the last rotation when the player is inactive + inactivityCounter++; + + if (worldSchematic == null) return false; + + if (PrinterConfig.TICK_DELAY.getIntegerValue() != 0 && delayCounter < PrinterConfig.TICK_DELAY.getIntegerValue()) { + delayCounter++; + return false; + } else { + delayCounter = 0; + } + + if (!LitematicaMixinMod.PRINT_MODE.getBooleanValue() && !LitematicaMixinMod.PRINT.getKeybind().isPressed()) + return false; + + PlayerAbilities abilities = player.getAbilities(); + if (!abilities.allowModifyWorld) + return false; + + if (PrinterConfig.STOP_ON_MOVEMENT.getBooleanValue() && player.getVelocity().length() > 0.1) + return false; // Stop if the player is moving + if (PrinterConfig.PRINTER_DISABLE_IN_GUIS.getBooleanValue()) { + if (mc.currentScreen != null) return false; + } + + List positions = getReachablePositions(); + + if (PrinterConfig.BLOCK_TIMEOUT.getIntegerValue() != 0) { + positions = positions.stream().filter((pos) -> blockPosTimeout.stream().noneMatch((entry) -> entry.pos.equals(pos))).toList(); // From block timeout. Don't place already placed blocks. + } + + 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); + + for (Guide guide : guides) { + if (guide.canExecute(player)) { + List actions = guide.execute(player); + actionHandler.addActions(actions.toArray(Action[]::new)); + return true; + } + if (guide.skipOtherGuides()) continue findBlock; + } + } + + return false; + } + + private List getBlocksPlayerOccupied() { + ArrayList positions = new ArrayList<>(); + BlockPos playerPos = player.getBlockPos(); + int blocksHeightOccupied = (int) Math.ceil(player.getPos().y + player.getHeight() - playerPos.getY()); + + positions.add(player.getBlockPos()); + positions.add(player.getBlockPos().up()); + if (blocksHeightOccupied > 2) { + positions.add(playerPos.up(2)); + } + if (Math.floor(player.getPos().x + player.getWidth() / 2) > playerPos.getX()) { + for (int i = 0; i < blocksHeightOccupied; i++) { + positions.add(playerPos.up(i).east()); + } + } + if ((player.getPos().x - player.getWidth() / 2) < playerPos.getX()) { + for (int i = 0; i < blocksHeightOccupied; i++) { + positions.add(playerPos.up(i).west()); + } + } + if (Math.floor(player.getPos().z + player.getWidth() / 2) > playerPos.getZ()) { + for (int i = 0; i < blocksHeightOccupied; i++) { + positions.add(playerPos.up(i).south()); + } + } + if ((player.getPos().z - player.getWidth() / 2) < playerPos.getZ()) { + for (int i = 0; i < blocksHeightOccupied; i++) { + positions.add(playerPos.up(i).north()); + } + } + return positions.stream().distinct().toList(); + } + + private List getReachablePositions() { + List playerOccupied = getBlocksPlayerOccupied(); + 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 -> playerOccupied.stream().noneMatch(p::equals)) + .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(); + } + + public static void addTimeout(BlockPos pos) { + blockPosTimeout.add(new BlockTimeout(pos, PrinterConfig.BLOCK_TIMEOUT.getIntegerValue())); + } + + public void rotate(float yaw, float pitch) { + LitematicaMixinMod.freeLook.ticksSinceLastRotation = 0; + this.player.setYaw(yaw); + this.player.setPitch(pitch); + } + + public static class BlockTimeout { + int timer = 0; + BlockPos pos; + + public BlockTimeout(BlockPos pos, int timer) { + this.pos = pos; + this.timer = timer; + } + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/SchematicBlockState.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/SchematicBlockState.java new file mode 100644 index 000000000..886d19b73 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/SchematicBlockState.java @@ -0,0 +1,42 @@ +package me.aleksilassila.litematica.printer.v1_21_4; + +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_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/SchematicConverter.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/SchematicConverter.java new file mode 100644 index 000000000..c86c11dab --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/SchematicConverter.java @@ -0,0 +1,23 @@ +package me.aleksilassila.litematica.printer.v1_21_4; + +import fi.dy.masa.litematica.schematic.LitematicaSchematic; +import fi.dy.masa.litematica.util.FileType; +import me.aleksilassila.litematica.printer.v1_21_4.mixin.LitematicaSchematicAccessor; + +import java.io.File; + +/** + * @author IceTank + * @since 17.12.2024 + */ +public class SchematicConverter { + public static LitematicaSchematic convertAndReturn(File file, File out) { + LitematicaSchematic schematic = LitematicaSchematicAccessor.invokeConstructor(file, FileType.VANILLA_STRUCTURE); + schematic.readFromFile(); + String fileName = file.getName().replace(".nbt", ""); + schematic.writeToFile(out, fileName, true); + LitematicaSchematic newSchem = LitematicaSchematicAccessor.invokeConstructor(new File(out, fileName + ".litematic"), FileType.LITEMATICA_SCHEMATIC); + newSchem.readFromFile(); + return newSchem; + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/UpdateChecker.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/UpdateChecker.java new file mode 100644 index 000000000..28730626a --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/UpdateChecker.java @@ -0,0 +1,43 @@ +package me.aleksilassila.litematica.printer.v1_21_4; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import java.io.InputStream; +import java.net.URL; +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_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/Action.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/Action.java new file mode 100644 index 000000000..72c0de908 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/Action.java @@ -0,0 +1,8 @@ +package me.aleksilassila.litematica.printer.v1_21_4.actions; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; + +public abstract class Action { + abstract public boolean send(MinecraftClient client, ClientPlayerEntity player); +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/ActionChain.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/ActionChain.java new file mode 100644 index 000000000..5c5de3b28 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/ActionChain.java @@ -0,0 +1,35 @@ +package me.aleksilassila.litematica.printer.v1_21_4.actions; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; + +import java.util.ArrayList; +import java.util.List; + +public class ActionChain extends Action { + List actionsCurrentTick = new ArrayList<>(); + List actionsNextTick = new ArrayList<>(); + + @Override + public boolean send(MinecraftClient client, ClientPlayerEntity player) { + throw new RuntimeException("ActionChain should not be sent. Manually send each preTick and postTick action."); + } + + public void addImmediateAction(Action action) { + actionsCurrentTick.add(action); + } + public void addNextTickAction(Action action) { + actionsNextTick.add(action); + } + + public void clear() { + actionsCurrentTick.clear(); + } + + public List getActionsCurrentTick() { + return actionsCurrentTick; + } + public List getActionsNextTick() { + return actionsNextTick; + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/InteractAction.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/InteractAction.java new file mode 100644 index 000000000..973574649 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/InteractAction.java @@ -0,0 +1,39 @@ +package me.aleksilassila.litematica.printer.v1_21_4.actions; + +import me.aleksilassila.litematica.printer.v1_21_4.LitematicaMixinMod; +import me.aleksilassila.litematica.printer.v1_21_4.Printer; +import me.aleksilassila.litematica.printer.v1_21_4.implementation.PrinterPlacementContext; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.BlockPos; + +abstract public class InteractAction extends Action { + public final PrinterPlacementContext context; + + public InteractAction(PrinterPlacementContext context) { + this.context = context; + } + + protected abstract ActionResult interact(MinecraftClient client, ClientPlayerEntity player, Hand hand, BlockHitResult hitResult); + + @Override + public boolean 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()); + BlockPos pos = context.hitResult.isInsideBlock() ? context.hitResult.getBlockPos() : context.hitResult.getBlockPos().offset(context.hitResult.getSide()); + Printer.addTimeout(pos); + return true; + } + + @Override + public String toString() { + return "InteractAction{" + + "context=" + context + + '}'; + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/PostAction.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/PostAction.java new file mode 100644 index 000000000..62e4e7d94 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/PostAction.java @@ -0,0 +1,30 @@ +package me.aleksilassila.litematica.printer.v1_21_4.actions; + +import me.aleksilassila.litematica.printer.v1_21_4.config.PrinterConfig; +import me.aleksilassila.litematica.printer.v1_21_4.implementation.PrinterPlacementContext; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket; + +public class PostAction extends PrepareAction { + public PostAction(PrinterPlacementContext context, ClientPlayerEntity player) { + super(context); + this.pitch = player.getPitch(); + this.yaw = player.getYaw(); + } + + @Override + public boolean send(MinecraftClient client, ClientPlayerEntity player) { + if (context.canStealth) { + PlayerMoveC2SPacket.LookAndOnGround packet = new PlayerMoveC2SPacket.LookAndOnGround(this.yaw, this.pitch, player.isOnGround(), player.horizontalCollision); + + if (PrinterConfig.ROTATE_PLAYER.getBooleanValue()) { + player.setYaw(this.yaw); + player.setPitch(this.pitch); + } + + player.networkHandler.sendPacket(packet); + } + return true; + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/PrepareAction.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/PrepareAction.java new file mode 100644 index 000000000..ae8c4bd86 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/PrepareAction.java @@ -0,0 +1,120 @@ +package me.aleksilassila.litematica.printer.v1_21_4.actions; + +import me.aleksilassila.litematica.printer.v1_21_4.Printer; +import me.aleksilassila.litematica.printer.v1_21_4.implementation.PrinterPlacementContext; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3d; +import org.jetbrains.annotations.Nullable; + +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; + + @Nullable + Direction lookDirection = context.lookDirection; + + if (lookDirection != null && lookDirection.getAxis().isHorizontal()) { + this.yaw = lookDirection.getUnitVector().y == 0 ? 0 : 90 * lookDirection.getUnitVector().y; + } 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; + } + + static float[] getNeededRotations(ClientPlayerEntity player, Vec3d vec) { + Vec3d eyesPos = player.getEyePos(); + + double diffX = vec.x - eyesPos.x; + double diffY = vec.y - eyesPos.y; + double diffZ = vec.z - eyesPos.z; + + double r = Math.sqrt(diffX * diffX + diffY * diffY + diffZ * diffZ); + double yaw = -Math.atan2(diffX, diffZ) / Math.PI * 180; + + double pitch = -Math.asin(diffY / r) / Math.PI * 180; + +// double diffXZ = Math.sqrt(diffX * diffX + diffZ * diffZ); +// +// float yaw = (float) Math.toDegrees(Math.atan2(diffZ, diffX)) - 90F; +// float pitch = (float) -Math.toDegrees(Math.atan2(diffY, diffXZ)); +// return new float[]{player.getYaw() + MathHelper.wrapDegrees(yaw - player.getYaw()), player.getPitch() + MathHelper.wrapDegrees(pitch - player.getPitch()) +// }; + return new float[]{(float) yaw, (float) pitch}; + } + + public static float clamp(float val, float min, float max) { + return Math.max(min, Math.min(max, val)); + } + + /** + * Returns the yaw difference between two yaw values. + * @param yaw1 The first yaw value. + * @param yaw2 The second yaw value. + * @return The yaw difference. + */ + public static float deltaYaw (float yaw1, float yaw2) { + final float PI_2 = (float) (Math.PI * 2); + float dYaw = (yaw1 - yaw2) % PI_2; + if (dYaw < -Math.PI) dYaw += PI_2; + else if (dYaw > Math.PI) dYaw -= PI_2; + + return dYaw; + } + + @Override + public boolean send(MinecraftClient client, ClientPlayerEntity player) { + ItemStack itemStack = context.getStack(); + + if (!Printer.inventoryManager.select(itemStack)) { + return false; + } + return true; + } + + @Override + public String toString() { + return "PrepareAction{" + + "context=" + context + + '}'; + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/PrepareLook.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/PrepareLook.java new file mode 100644 index 000000000..ca9501248 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/PrepareLook.java @@ -0,0 +1,116 @@ +package me.aleksilassila.litematica.printer.v1_21_4.actions; + +import me.aleksilassila.litematica.printer.v1_21_4.LitematicaMixinMod; +import me.aleksilassila.litematica.printer.v1_21_4.MovementHandler; +import me.aleksilassila.litematica.printer.v1_21_4.Printer; +import me.aleksilassila.litematica.printer.v1_21_4.config.PrinterConfig; +import me.aleksilassila.litematica.printer.v1_21_4.implementation.PrinterPlacementContext; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3d; + +import java.util.Optional; + +public class PrepareLook extends Action { + private static final MinecraftClient mc = MinecraftClient.getInstance(); + public final PrinterPlacementContext context; + public Optional yaw = Optional.empty(); + public Optional pitch = Optional.empty(); + + public PrepareLook(PrinterPlacementContext context) { + this.context = context; + } + + static float[] getNeededRotations(ClientPlayerEntity player, Vec3d vec) { + Vec3d eyesPos = player.getEyePos(); + + double diffX = vec.x - eyesPos.x; + double diffY = vec.y - eyesPos.y; + double diffZ = vec.z - eyesPos.z; + + double r = Math.sqrt(diffX * diffX + diffY * diffY + diffZ * diffZ); + double yaw = -Math.atan2(diffX, diffZ) / Math.PI * 180; + + double pitch = -Math.asin(diffY / r) / Math.PI * 180; + + return new float[]{(float) yaw, (float) pitch}; + } + + static float[] getRotation(Direction direction) { + switch (direction) { + case NORTH: + return new float[]{180.0f, 0.0f}; + case SOUTH: + return new float[]{0.0f, 0.0f}; + case WEST: + return new float[]{90.0f, 0.0f}; + case EAST: + return new float[]{-90.0f, 0.0f}; + case UP: + return new float[]{0.0f, -90.0f}; + case DOWN: + return new float[]{0.0f, 90.0f}; + default: + return new float[]{0.0f, 0.0f}; + } + } + + @Override + public boolean send(MinecraftClient client, ClientPlayerEntity player) { + if (context.isAirPlace) { + if (context.lookDirection != null) { + float[] targetRot = getRotation(context.lookDirection); + this.yaw = Optional.of(targetRot[0]); + this.pitch = Optional.of(targetRot[1]); + + if (PrinterConfig.PRINTER_DEBUG_LOG.getBooleanValue()) + Printer.logger.info("Sending yaw for modified airplace yaw: " + yaw + ", pitch: " + pitch); + + if (PrinterConfig.PRINTER_GRIM_ROTATION.getBooleanValue()) { + MovementHandler.grimRotate(player, yaw.get(), pitch.get()); + } else if (PrinterConfig.ROTATE_PLAYER.getBooleanValue()) { + LitematicaMixinMod.printer.rotate(yaw.get(), pitch.get()); + } else { + mc.getNetworkHandler().sendPacket(new PlayerMoveC2SPacket.LookAndOnGround( + yaw.get(), pitch.get(), player.isOnGround(), player.horizontalCollision)); + } + return true; + } + } + if (context.canStealth) { + float[] targetRot = getNeededRotations(player, context.getHitPos()); + + if (PrinterConfig.ROTATE_PLAYER.getBooleanValue()) { + LitematicaMixinMod.printer.rotate(targetRot[0], targetRot[1]); + } + + this.yaw = Optional.of(targetRot[0]); + this.pitch = Optional.of(targetRot[1]); + if (PrinterConfig.PRINTER_GRIM_ROTATION.getBooleanValue()) { + MovementHandler.grimRotate(player, targetRot[0], targetRot[1]); + } + } else { + float yaw = player.getYaw(); + float pitch = player.getPitch(); + + if (PrinterConfig.PRINTER_DEBUG_LOG.getBooleanValue()) + System.out.println("Sending yaw for modified yaw: " + yaw + ", pitch: " + pitch); + + this.yaw = Optional.of(yaw); + this.pitch = Optional.of(pitch); + if (PrinterConfig.PRINTER_GRIM_ROTATION.getBooleanValue()) { + MovementHandler.grimRotate(player, yaw, pitch); + } + } + return true; + } + + @Override + public String toString() { + return "PrepareAction{" + + "context=" + context + + '}'; + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/PresShift.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/PresShift.java new file mode 100644 index 000000000..84a7a456c --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/PresShift.java @@ -0,0 +1,17 @@ +package me.aleksilassila.litematica.printer.v1_21_4.actions; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.network.packet.c2s.play.ClientCommandC2SPacket; +import net.minecraft.util.PlayerInput; + +public class PresShift extends Action { + @Override + public boolean send(MinecraftClient client, ClientPlayerEntity player) { + player.input.playerInput = new PlayerInput(player.input.playerInput.forward(), + player.input.playerInput.backward(), player.input.playerInput.left(), player.input.playerInput.right(), + player.input.playerInput.jump(), true, player.input.playerInput.sprint()); + player.networkHandler.sendPacket(new ClientCommandC2SPacket(player, ClientCommandC2SPacket.Mode.PRESS_SHIFT_KEY)); + return true; + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/ReleaseShiftAction.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/ReleaseShiftAction.java new file mode 100644 index 000000000..164ec3906 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/ReleaseShiftAction.java @@ -0,0 +1,20 @@ +package me.aleksilassila.litematica.printer.v1_21_4.actions; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.network.packet.c2s.play.ClientCommandC2SPacket; +import net.minecraft.util.PlayerInput; + +public class ReleaseShiftAction extends Action { + private static final MinecraftClient mc = MinecraftClient.getInstance(); + @Override + public boolean send(MinecraftClient client, ClientPlayerEntity player) { + player.input.playerInput = new PlayerInput(player.input.playerInput.forward(), + player.input.playerInput.backward(), player.input.playerInput.left(), player.input.playerInput.right(), + player.input.playerInput.jump(), mc.options.sneakKey.isPressed(), player.input.playerInput.sprint()); + if (!mc.options.sneakKey.isPressed()) { + player.networkHandler.sendPacket(new ClientCommandC2SPacket(player, ClientCommandC2SPacket.Mode.RELEASE_SHIFT_KEY)); + } + return true; + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/config/FreeLookKeyCallbackToggle.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/config/FreeLookKeyCallbackToggle.java new file mode 100644 index 000000000..04e530b62 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/config/FreeLookKeyCallbackToggle.java @@ -0,0 +1,26 @@ +package me.aleksilassila.litematica.printer.v1_21_4.config; + +import fi.dy.masa.malilib.config.options.ConfigBoolean; +import fi.dy.masa.malilib.hotkeys.IKeybind; +import fi.dy.masa.malilib.hotkeys.KeyAction; +import fi.dy.masa.malilib.hotkeys.KeyCallbackToggleBooleanConfigWithMessage; +import me.aleksilassila.litematica.printer.v1_21_4.LitematicaMixinMod; +import net.minecraft.client.MinecraftClient; + +public class FreeLookKeyCallbackToggle extends KeyCallbackToggleBooleanConfigWithMessage { + MinecraftClient mc = MinecraftClient.getInstance(); + public FreeLookKeyCallbackToggle(ConfigBoolean config) { + super(config); + } + + @Override + public boolean onKeyAction(KeyAction action, IKeybind key) { + super.onKeyAction(action, key); + + if (!config.getBooleanValue() && mc.player != null) { + LitematicaMixinMod.movementHandler.onDisable(mc.player); + } + + return true; + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/config/PrinterConfig.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/config/PrinterConfig.java new file mode 100644 index 000000000..bac08677a --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/config/PrinterConfig.java @@ -0,0 +1,185 @@ +package me.aleksilassila.litematica.printer.v1_21_4.config; + +import com.google.common.collect.ImmutableList; +import fi.dy.masa.litematica.config.Configs; +import fi.dy.masa.malilib.MaLiLib; +import fi.dy.masa.malilib.config.IConfigBase; +import fi.dy.masa.malilib.config.IConfigOptionListEntry; +import fi.dy.masa.malilib.config.options.*; +import fi.dy.masa.malilib.event.InputEventHandler; +import fi.dy.masa.malilib.hotkeys.KeybindSettings; +import me.aleksilassila.litematica.printer.v1_21_4.InventoryManager; +import me.aleksilassila.litematica.printer.v1_21_4.Printer; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.config.Configurator; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class PrinterConfig { + private PrinterConfig() { + } + + @Nullable + public static PrinterConfig INSTANCE; + + public static PrinterConfig getInstance() { + if (INSTANCE == null) { + INSTANCE = new PrinterConfig(); + } + return INSTANCE; + } + + public static final ConfigInteger TICK_DELAY = new ConfigInteger("printerTickDelay", 0, 0, 100, "Tick delay between actions. 0 = no delay. Printer is limited to one action per tick."); + public static final ConfigInteger BLOCK_TIMEOUT = new ConfigInteger("printerBlockTimeout", 10, 0, 100, "How many ticks to wait before trying to place the same block again."); + public static final ConfigBoolean ROTATE_PLAYER = new ConfigBoolean("printerRotatePlayer", false, "Rotate the player to face the block to place."); + public static final ConfigBoolean PRINTER_GRIM_ROTATION = new ConfigBoolean("printerGrimRotate", true, "Allows you to place blocks anywhere while walking or using baritone."); + public static final ConfigBoolean STOP_ON_MOVEMENT = new ConfigBoolean("printerStopOnMovement", false, "Stop the printer if the player velocity is to high."); + public static final ConfigInteger INVENTORY_DELAY = new ConfigInteger("printerInventoryDelay", 10, 0, 100, "The delay between each inventory action. 0 = no delay."); + public static final ConfigOptionList PRINTER_INVENTORY_MANAGEMENT_MODE = new ConfigOptionList("printerInventoryManagementMode", InventoryManagementModeEnum.LEAST_USED, "Inventory management mode. Rolling = cycle through the hotbar, Least Used = use the least used slot in the hotbar."); + public static final ConfigBoolean RAYCAST = new ConfigBoolean("printerRaycast", false, "Raycast the block to place to check if it is obstructed by another block. Most anti cheat don't check for this."); + public static final ConfigBoolean NO_PLACEMENT_CACHE = new ConfigBoolean("printerNoPlacementCache", false, "Disable the placement cache. This may cause more lag."); + public static final ConfigBoolean RAYCAST_STRICT_BLOCK_HIT = new ConfigBoolean("printerRaycastStrictBlockHit", false, "Check if the right side of the block is hit when raycasting."); + public static final ConfigBoolean PREVENT_DOUBLE_TAP_SPRINTING = new ConfigBoolean("printerPreventDoubleTapSprinting", false, "Prevent double tap sprinting when the printer is active."); + public static final ConfigBoolean MOVE_WHILE_IN_INVENTORY = new ConfigBoolean("printerMoveWhileInInventory", false, "Allows the player to move while the player inventory is open."); + public static final ConfigBoolean FREE_LOOK = new ConfigBoolean("printerFreeLook", false, "Free look mode. Allows you to look around while the printer is active."); + public static final ConfigHotkey FREE_LOOK_TOGGLE = new ConfigHotkey("printerFreeLookToggle", "", KeybindSettings.MODIFIER_INGAME, "Free look mode. Allows you to look around while the printer is active."); + public static final ConfigBoolean FREE_LOOK_THIRD_PERSON = new ConfigBoolean("printerFreeLookThirdPerson", true, "Auto switches the camera to third person when free look is enabled."); + public static final ConfigInteger FREE_LOOK_LOOK_BACK = new ConfigInteger("printerFreeLookLookBackDelay", 1, 0, 100, "Time in ticks until the player character is rotated back to the camera view. 0 to disable."); + public static final ConfigBoolean FREE_LOOK_LOOK_BACK_ALWAYS_ROTATE_PLAYER = new ConfigBoolean("printerFreeLookLookBackAlwaysRotatePlayer", false, "Always rotate the player back to the camera view.\nMakes it more compatible with Baritone edge cases but is more intrusive and might cause other issues."); + public static final ConfigBoolean STRICT_BLOCK_FACE_CHECK = new ConfigBoolean("printerStrictBlockFaceCheck", true, "Places only against block faces that are facing the player."); + public static final ConfigHotkey PRINTER_PICK_BLOCK = new ConfigHotkey("printerPickBlock", "MIDDLE_MOUSE", KeybindSettings.PRESS_ALLOWEXTRA_EMPTY, "Pick a block from the schematic preview to the hotbar.\nAlso works for materials in shulkers when PICK_BLOCK_SHULKERS is enabled."); + public static final ConfigBoolean PRINTER_DEBUG_LOG = new ConfigBoolean("printerDebugLog", false, "Print debug messages to the console."); + public static final ConfigBoolean PRINTER_IGNORE_ROTATION = new ConfigBoolean("printerIgnoreRotation", false, "Ignore the block rotation when placing."); + public static final ConfigBoolean PRINTER_ALLOW_NONE_EXACT_STATES = new ConfigBoolean("printerAllowNoneExactStates", false, "Allow none exact block states to be placed.\nThis includes things like lichen, muchroom stems, etc."); + public static final ConfigBoolean PRINTER_DISABLE_IN_GUIS = new ConfigBoolean("printerDisableInGuis", true, "Disable the printer in GUIs."); + public static final ConfigBoolean PRINTER_AIRPLACE = new ConfigBoolean("printerAirPlace", false, "Place blocks in the air."); + public static final ConfigBoolean PRINTER_AIRPLACE_ONLY = new ConfigBoolean("printerAirPlaceOnly", false, "Attempt to air place only when air place is enabled."); + public static final ConfigBoolean PRINTER_AIRPLACE_OFFHAND_SLOT_SUPPRESS = new ConfigBoolean("printerAirPlaceOffhandSlotSuppress", true, "Suppress off-hand slot updates when air placing. Turn off when there are de-sync issues in the off-hand slot."); + public static final ConfigBoolean PRINTER_SUPER_CHINESE_GHOST_ITEM_FIX = new ConfigBoolean("printerSuperChineseGhostItemFix", true, "Fixes ghost items in the inventory when placing a lot of blocks really fast."); + public static final ConfigDouble PRINTER_AIRPLACE_RANGE = new ConfigDouble("printerAirPlaceRange", 5, 0, 10, "Range at which the printer can air place at"); + public static final ConfigBoolean PRINTER_AIRPLACE_FLOATING_ONLY = new ConfigBoolean("printerAirPlaceFloatingOnly", false, "Only attempt to air place if the block position is surrounded by air."); + public static final ConfigInteger PRINTER_MIN_INACTIVE_TIME_AIR_PLACE = new ConfigInteger("printerMinInactiveTimeAirPlace", 0, "Minimum time in ticks to wait before placing a block in the air."); + public static final ConfigString PRINTER_HOTBAR_SLOTS = new ConfigString("printerHotbarSlots", "3,4,5,6,7,8,9", "Hotbar slots to use for the printer. Numbers from 1-9 separated by commas."); + public static final ConfigBoolean AUTO_CONVERT_SCHEMATIC_TO_LITEMATIC_ON_LOAD = new ConfigBoolean("autoConvertSchematicToLitematicOnLoad", false, "Automatically convert schematic files to litematic files when loading them."); + public static final ConfigBoolean PRINTER_PLACE_OBSERVERS_LAST = new ConfigBoolean("printerPlaceObserversLast", false, "Only place observers when the block infront of them in the schematic is either air or already placed in the world."); + public static final ConfigBoolean WATERLOGGING = new ConfigBoolean("printerWaterlogging", false, "Automatically waterlog blocks with water buckets that should be waterlogged in the schematic."); + + public ImmutableList getOptions() { + List list = new java.util.ArrayList<>(Configs.Generic.OPTIONS); + list.add(TICK_DELAY); + list.add(BLOCK_TIMEOUT); + list.add(ROTATE_PLAYER); + list.add(PRINTER_GRIM_ROTATION); + list.add(STOP_ON_MOVEMENT); + list.add(INVENTORY_DELAY); + list.add(PRINTER_INVENTORY_MANAGEMENT_MODE); + list.add(RAYCAST); + list.add(NO_PLACEMENT_CACHE); + list.add(RAYCAST_STRICT_BLOCK_HIT); + list.add(PREVENT_DOUBLE_TAP_SPRINTING); + list.add(MOVE_WHILE_IN_INVENTORY); + list.add(FREE_LOOK); + list.add(FREE_LOOK_TOGGLE); + list.add(STRICT_BLOCK_FACE_CHECK); + list.add(FREE_LOOK_THIRD_PERSON); + list.add(FREE_LOOK_LOOK_BACK); + list.add(FREE_LOOK_LOOK_BACK_ALWAYS_ROTATE_PLAYER); + list.add(PRINTER_PICK_BLOCK); + list.add(PRINTER_DEBUG_LOG); + list.add(PRINTER_IGNORE_ROTATION); + list.add(PRINTER_ALLOW_NONE_EXACT_STATES); + list.add(PRINTER_DISABLE_IN_GUIS); + list.add(PRINTER_AIRPLACE); + list.add(PRINTER_AIRPLACE_ONLY); + list.add(PRINTER_AIRPLACE_OFFHAND_SLOT_SUPPRESS); + list.add(PRINTER_SUPER_CHINESE_GHOST_ITEM_FIX); + list.add(PRINTER_AIRPLACE_RANGE); + list.add(PRINTER_AIRPLACE_FLOATING_ONLY); + list.add(PRINTER_MIN_INACTIVE_TIME_AIR_PLACE); + list.add(PRINTER_HOTBAR_SLOTS); + list.add(AUTO_CONVERT_SCHEMATIC_TO_LITEMATIC_ON_LOAD); + list.add(PRINTER_PLACE_OBSERVERS_LAST); + list.add(WATERLOGGING); + + PRINTER_DEBUG_LOG.setValueChangeCallback(config -> { + if (config.getBooleanValue()) { + MaLiLib.LOGGER.info("Printer debug logging enabled"); + Configurator.setLevel(LogManager.getLogger(Printer.logger.getName()), Level.DEBUG); + } else { + MaLiLib.LOGGER.info("Printer debug logging disabled"); + Configurator.setLevel(LogManager.getLogger(Printer.logger.getName()), Level.INFO); + } + }); + + PRINTER_HOTBAR_SLOTS.setValueChangeCallback(InventoryManager::setHotbarSlots); + + return ImmutableList.copyOf(list); + } + + public static boolean isDebug() { + return PRINTER_DEBUG_LOG.getBooleanValue(); + } + + public static void onInitialize() { + MaLiLib.LOGGER.info("PrinterConfig.onInitialize"); + FREE_LOOK_TOGGLE.getKeybind().setCallback(new FreeLookKeyCallbackToggle(FREE_LOOK)); + PRINTER_PICK_BLOCK.getKeybind().setCallback(new PrinterPickBlockKeyCallback()); + InputEventHandler.getKeybindManager().registerKeybindProvider(PrinterInputHandler.getInstance()); + } + + public static void onConfigFileLoad() { + InventoryManager.setHotbarSlots(PRINTER_HOTBAR_SLOTS); + } + + public enum InventoryManagementModeEnum implements IConfigOptionListEntry { + ROLLING("rolling", "Rolling"), + LEAST_USED("leastUsed", "Least Used"); + + final String name; + final String displayName; + + InventoryManagementModeEnum(String name, String displayName) { + this.name = name; + this.displayName = displayName; + } + + public boolean is(InventoryManagementModeEnum mode) { + return this.ordinal() == mode.ordinal(); + } + + public boolean is(String string) { + return this.name.equalsIgnoreCase(string); + } + + @Override + public String getStringValue() { + return name; + } + + @Override + public String getDisplayName() { + return displayName; + } + + @Override + public IConfigOptionListEntry cycle(boolean forward) { + int current = this.ordinal(); + int next = forward ? current + 1 : current - 1; + if (next < 0) next = InventoryManagementModeEnum.values().length - 1; + if (next >= InventoryManagementModeEnum.values().length) next = 0; + return InventoryManagementModeEnum.values()[next]; + } + + @Override + public IConfigOptionListEntry fromString(String value) { + for (InventoryManagementModeEnum mode : InventoryManagementModeEnum.values()) { + if (mode.name.equalsIgnoreCase(value)) { + return mode; + } + } + return this; + } + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/config/PrinterInputHandler.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/config/PrinterInputHandler.java new file mode 100644 index 000000000..55c80f315 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/config/PrinterInputHandler.java @@ -0,0 +1,25 @@ +package me.aleksilassila.litematica.printer.v1_21_4.config; + +import fi.dy.masa.malilib.hotkeys.IKeybindManager; +import fi.dy.masa.malilib.hotkeys.IKeybindProvider; + +public class PrinterInputHandler implements IKeybindProvider { + static PrinterInputHandler INSTANCE; + + public static PrinterInputHandler getInstance() { + if (INSTANCE == null) { + INSTANCE = new PrinterInputHandler(); + } + return INSTANCE; + } + @Override + public void addKeysToMap(IKeybindManager manager) { + manager.addKeybindToMap(PrinterConfig.FREE_LOOK_TOGGLE.getKeybind()); + manager.addKeybindToMap(PrinterConfig.PRINTER_PICK_BLOCK.getKeybind()); + } + + @Override + public void addHotkeys(IKeybindManager manager) { + + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/config/PrinterPickBlockKeyCallback.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/config/PrinterPickBlockKeyCallback.java new file mode 100644 index 000000000..74af8eb52 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/config/PrinterPickBlockKeyCallback.java @@ -0,0 +1,14 @@ +package me.aleksilassila.litematica.printer.v1_21_4.config; + +import fi.dy.masa.malilib.hotkeys.IHotkeyCallback; +import fi.dy.masa.malilib.hotkeys.IKeybind; +import fi.dy.masa.malilib.hotkeys.KeyAction; +import me.aleksilassila.litematica.printer.v1_21_4.LitematicaMixinMod; + +public class PrinterPickBlockKeyCallback implements IHotkeyCallback { + @Override + public boolean onKeyAction(KeyAction action, IKeybind key) { + LitematicaMixinMod.printer.onMiddleClick(); + return true; + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/Guide.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/Guide.java new file mode 100644 index 000000000..00d40aed3 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/Guide.java @@ -0,0 +1,147 @@ +package me.aleksilassila.litematica.printer.v1_21_4.guides; + +import me.aleksilassila.litematica.printer.v1_21_4.SchematicBlockState; +import me.aleksilassila.litematica.printer.v1_21_4.actions.Action; +import me.aleksilassila.litematica.printer.v1_21_4.config.PrinterConfig; +import me.aleksilassila.litematica.printer.v1_21_4.guides.placement.PropertySpecificGuesserGuide; +import me.aleksilassila.litematica.printer.v1_21_4.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; + } + + public 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; + } + + ItemStack requiredItem = getRequiredItem(player).stream().findFirst().orElse(ItemStack.EMPTY); + if (requiredItem.isEmpty()) return -1; + + return getSlotWithItem(player, requiredItem); + } + + 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 List getRequiredItem(ClientPlayerEntity player) { + List requiredItems = getRequiredItems(); + + for (ItemStack requiredItem : requiredItems) { + if (player.getAbilities().creativeMode) return List.of(requiredItem); + + int slot = getSlotWithItem(player, requiredItem); + if (slot > -1) + return List.of(requiredItem); + } + + return List.of(); + } + + protected boolean statesEqualIgnoreProperties(BlockState state1, BlockState state2, Property... propertiesToIgnore) { + if (state1.getBlock() != state2.getBlock()) return false; + + loop: + for (Property property : state1.getProperties()) { + if (!PrinterConfig.WATERLOGGING.getBooleanValue() && 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 the two states are equal, ignoring properties that are not relevant + */ + protected boolean statesEqual(BlockState state1, BlockState state2) { + // Always ignore redstone-volatile properties that can differ transiently in-world + // to avoid blocking placement under redstone power. + Property[] redstoneVolatile = new Property[] { + Properties.POWERED, // many blocks (buttons, rails, etc.) + Properties.TRIGGERED, // dispensers/dropper + Properties.ENABLED // hoppers + }; + + if (PrinterConfig.PRINTER_IGNORE_ROTATION.getBooleanValue()) { + // Merge rotation ignore with redstone-volatile ignores + Property[] merged = new Property[PropertySpecificGuesserGuide.rotationProperties.length + redstoneVolatile.length]; + System.arraycopy(PropertySpecificGuesserGuide.rotationProperties, 0, merged, 0, PropertySpecificGuesserGuide.rotationProperties.length); + System.arraycopy(redstoneVolatile, 0, merged, PropertySpecificGuesserGuide.rotationProperties.length, redstoneVolatile.length); + return statesEqualIgnoreProperties(state1, state2, merged); + } else { + return statesEqualIgnoreProperties(state1, state2, redstoneVolatile); + } + } + + public boolean skipOtherGuides() { + return false; + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/Guides.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/Guides.java new file mode 100644 index 000000000..080b1d544 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/Guides.java @@ -0,0 +1,81 @@ +package me.aleksilassila.litematica.printer.v1_21_4.guides; + +import me.aleksilassila.litematica.printer.v1_21_4.SchematicBlockState; +import me.aleksilassila.litematica.printer.v1_21_4.guides.interaction.*; +import me.aleksilassila.litematica.printer.v1_21_4.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(FacingBlockGuide.class, StairsBlock.class, GlazedTerracottaBlock.class); + registerGuide(SlabGuide.class, SlabBlock.class); + registerGuide(TorchGuide.class, TorchBlock.class, WallRedstoneTorchBlock.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(FallingBlockGuide.class, FallingBlock.class); + registerGuide(BlockIndifferentGuesserGuide.class, BambooBlock.class, BigDripleafStemBlock.class, BigDripleafBlock.class, + TwistingVinesPlantBlock.class, TripwireBlock.class, MushroomBlock.class, MultifaceGrowthBlock.class); + + registerGuide(CampfireExtinguishGuide.class, CampfireBlock.class); + registerGuide(LightCandleGuide.class, AbstractCandleBlock.class); + registerGuide(EnderEyeGuide.class, EndPortalFrameBlock.class); + + // Catch most blocks + registerGuide(PropertySpecificGuesserGuide.class); +// registerGuide(LogGuide.class); + + // Anything that replaces or changes stuff after placement + 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(WaterLogGuide.class); + registerGuide(LogStrippingGuide.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_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/SkipGuide.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/SkipGuide.java new file mode 100644 index 000000000..ffd21a719 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/SkipGuide.java @@ -0,0 +1,37 @@ +package me.aleksilassila.litematica.printer.v1_21_4.guides; + +import me.aleksilassila.litematica.printer.v1_21_4.SchematicBlockState; +import me.aleksilassila.litematica.printer.v1_21_4.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_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/CampfireExtinguishGuide.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/CampfireExtinguishGuide.java new file mode 100644 index 000000000..deea1056f --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/CampfireExtinguishGuide.java @@ -0,0 +1,34 @@ +package me.aleksilassila.litematica.printer.v1_21_4.guides.interaction; + +import me.aleksilassila.litematica.printer.v1_21_4.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_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/CycleStateGuide.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/CycleStateGuide.java new file mode 100644 index 000000000..cce0e8b60 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/CycleStateGuide.java @@ -0,0 +1,59 @@ +package me.aleksilassila.litematica.printer.v1_21_4.guides.interaction; + +import me.aleksilassila.litematica.printer.v1_21_4.SchematicBlockState; +import net.minecraft.block.*; +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, + Properties.BLOCK_FACE, + Properties.FACING, + Properties.LOCKED, + Properties.BLOCK_HALF, + Properties.DOOR_HINGE, + Properties.IN_WALL, + RepeaterBlock.FACING + }; + + public CycleStateGuide(SchematicBlockState state) { + super(state); + } + + @Override + public boolean canExecute(ClientPlayerEntity player) { + if (!playerHasRightItem(player)) return false; + + if (currentState.getBlock() == Blocks.IRON_TRAPDOOR) { + return false; // Iron trapdoors cannot be toggled by interaction + } + + if (currentState.getBlock() != targetState.getBlock()) { + return false; // Different blocks cannot be toggled + } + + BlockState targetState = state.targetState; + BlockState currentState = state.currentState; + + if (currentState.getBlock() == Blocks.LEVER) { + if (currentState.get(LeverBlock.POWERED) == targetState.get(LeverBlock.POWERED)) { + return false; // Lever blocks must be toggled if POWERED property is incorrect, regardless of other properties + } + return true; + } + return !statesEqualIgnoreProperties(targetState, currentState, propertiesToIgnore); + } + + @Override + protected @NotNull List getRequiredItems() { + return Collections.singletonList(ItemStack.EMPTY); + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/EnderEyeGuide.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/EnderEyeGuide.java new file mode 100644 index 000000000..caffef5d1 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/EnderEyeGuide.java @@ -0,0 +1,33 @@ +package me.aleksilassila.litematica.printer.v1_21_4.guides.interaction; + +import me.aleksilassila.litematica.printer.v1_21_4.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_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/FlowerPotFillGuide.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/FlowerPotFillGuide.java new file mode 100644 index 000000000..108be2f42 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/FlowerPotFillGuide.java @@ -0,0 +1,40 @@ +package me.aleksilassila.litematica.printer.v1_21_4.guides.interaction; + +import me.aleksilassila.litematica.printer.v1_21_4.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_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/InteractionGuide.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/InteractionGuide.java new file mode 100644 index 000000000..18b029bec --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/InteractionGuide.java @@ -0,0 +1,54 @@ +package me.aleksilassila.litematica.printer.v1_21_4.guides.interaction; + +import me.aleksilassila.litematica.printer.v1_21_4.actions.ActionChain; +import me.aleksilassila.litematica.printer.v1_21_4.implementation.PrinterPlacementContext; +import me.aleksilassila.litematica.printer.v1_21_4.SchematicBlockState; +import me.aleksilassila.litematica.printer.v1_21_4.actions.Action; +import me.aleksilassila.litematica.printer.v1_21_4.actions.PrepareAction; +import me.aleksilassila.litematica.printer.v1_21_4.actions.ReleaseShiftAction; +import me.aleksilassila.litematica.printer.v1_21_4.guides.Guide; +import me.aleksilassila.litematica.printer.v1_21_4.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).stream().findFirst().orElse(ItemStack.EMPTY); + int requiredSlot = getRequiredItemStackSlot(player); + + if (requiredSlot == -1) return actions; + + PrinterPlacementContext ctx = new PrinterPlacementContext(player, hitResult, requiredItem, requiredSlot); + + ActionChain chain = new ActionChain(); + + chain.addImmediateAction(new ReleaseShiftAction()); + chain.addImmediateAction(new PrepareAction(ctx)); + chain.addImmediateAction(new InteractActionImpl(ctx)); + + actions.add(chain); + + return actions; + } + + @Override + abstract protected @NotNull List getRequiredItems(); +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/LightCandleGuide.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/LightCandleGuide.java new file mode 100644 index 000000000..e3bbc3122 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/LightCandleGuide.java @@ -0,0 +1,36 @@ +package me.aleksilassila.litematica.printer.v1_21_4.guides.interaction; + +import me.aleksilassila.litematica.printer.v1_21_4.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_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/LogStrippingGuide.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/LogStrippingGuide.java new file mode 100644 index 000000000..0f9f56748 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/LogStrippingGuide.java @@ -0,0 +1,47 @@ +package me.aleksilassila.litematica.printer.v1_21_4.guides.interaction; + +import me.aleksilassila.litematica.printer.v1_21_4.LitematicaMixinMod; +import me.aleksilassila.litematica.printer.v1_21_4.SchematicBlockState; +import me.aleksilassila.litematica.printer.v1_21_4.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_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/TillingGuide.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/TillingGuide.java new file mode 100644 index 000000000..a2d2a99c8 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/TillingGuide.java @@ -0,0 +1,39 @@ +package me.aleksilassila.litematica.printer.v1_21_4.guides.interaction; + +import me.aleksilassila.litematica.printer.v1_21_4.SchematicBlockState; +import me.aleksilassila.litematica.printer.v1_21_4.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_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/WaterLogGuide.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/WaterLogGuide.java new file mode 100644 index 000000000..fb82287ab --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/interaction/WaterLogGuide.java @@ -0,0 +1,138 @@ +package me.aleksilassila.litematica.printer.v1_21_4.guides.interaction; + + +import me.aleksilassila.litematica.printer.v1_21_4.LitematicaMixinMod; +import me.aleksilassila.litematica.printer.v1_21_4.SchematicBlockState; +import me.aleksilassila.litematica.printer.v1_21_4.actions.*; +import me.aleksilassila.litematica.printer.v1_21_4.guides.placement.GeneralPlacementGuide; +import me.aleksilassila.litematica.printer.v1_21_4.implementation.PrinterPlacementContext; +import me.aleksilassila.litematica.printer.v1_21_4.implementation.actions.UseItemActionImpl; +import net.minecraft.block.Waterloggable; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.state.property.Properties; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.hit.HitResult; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.RaycastContext; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/* + * @author IceTank + * @since 08.03.2026 + */ +public class WaterLogGuide extends GeneralPlacementGuide { + boolean canWork = false; + + public WaterLogGuide(SchematicBlockState state) { + super(state); + canWork = state.targetState.getBlock() instanceof Waterloggable; + } + + @Override + public boolean canExecute(ClientPlayerEntity player) { + if (!canWork) return false; + if (!(currentState.getBlock() instanceof Waterloggable)) return false; + + if (currentState.get(Properties.WATERLOGGED) == null + || currentState.get(Properties.WATERLOGGED) == targetState.get(Properties.WATERLOGGED)) { + return false; + } + +// if (!super.canExecute(player)) return false; + List requiredItems = getRequiredItems(); + if (requiredItems.isEmpty() || requiredItems.stream().allMatch(i -> i.isOf(Items.AIR))) + return false; + for (Direction side : getPossibleSides()) { + if (canSeeBlockFace(player, new BlockHitResult(Vec3d.ofCenter(state.blockPos), side.getOpposite(), state.blockPos.offset(side), false))) { + return true; + } + } + return false; + } + + @Override + protected @NotNull List getRequiredItems() { + if (!canWork) return Collections.emptyList(); + return Collections.singletonList(new ItemStack(Items.WATER_BUCKET)); + } + + @Override + public @Nullable PrinterPlacementContext getPlacementContext(ClientPlayerEntity player) { + ClientWorld world = MinecraftClient.getInstance().world; + if (world == null) return null; + + Vec3d[] hitVecsToTryArray = getPossibleHitVecs(); + Vec3d playerEyePos = player.getEyePos(); + + ItemStack requiredItem = getRequiredItem(player).stream().findFirst().orElse(ItemStack.EMPTY); + int slot = getRequiredItemStackSlot(player); + + if (slot == -1) return null; + + for (Direction side : getPossibleSides()) { + Vec3d hitVec = Vec3d.ofCenter(state.blockPos) + .add(Vec3d.of(side.getVector()).multiply(0.5)); // Center of the block side face we are placing on + + for (Vec3d hitVecToTry : hitVecsToTryArray) { + Vec3d multiplier = Vec3d.of(side.getVector()); + multiplier = new Vec3d( + multiplier.x == 0 ? 1 : 0, + multiplier.y == 0 ? 1 : 0, + multiplier.z == 0 ? 1 : 0); // Offset from the Center of the block side face we are placing on by pre calculated values. This samples different points on that face. + + Vec3d blockHit = hitVec.add(hitVecToTry.multiply(multiplier)); + Vec3d lookDirection = blockHit.subtract(playerEyePos).normalize(); + Direction relativeDirection = Direction.getFacing(lookDirection.x, lookDirection.y, lookDirection.z); + + if (playerEyePos.distanceTo(blockHit) > LitematicaMixinMod.PRINTING_RANGE.getDoubleValue()) // Check if the hit vector is in range + continue; + + Vec3d lookVec = blockHit.subtract(playerEyePos).normalize(); // Look vector from the player's eye to the block hit vector + Vec3d raycastEnd = playerEyePos.add(lookVec.multiply(5)); // 5 block max distance + RaycastContext raycastContext = new RaycastContext(playerEyePos, raycastEnd, RaycastContext.ShapeType.OUTLINE, RaycastContext.FluidHandling.NONE, player); + BlockHitResult result = world.raycast(raycastContext); + if (result.getType() != HitResult.Type.BLOCK || !result.getBlockPos().equals(state.blockPos)) { // If we didn't hit a block, skip + continue; + } + if (result.getPos().distanceTo(playerEyePos) > LitematicaMixinMod.PRINTING_RANGE.getDoubleValue()) { // Check if the hit result is in range + continue; + } + + PrinterPlacementContext rayTraceContext = new PrinterPlacementContext(player, result, requiredItem, slot, relativeDirection, false); + rayTraceContext.canStealth = true; + rayTraceContext.isRaytrace = true; + return rayTraceContext; + } + } + return null; + } + + @Override + public @NotNull List execute(ClientPlayerEntity player) { + List actions = new ArrayList<>(); + + ItemStack requiredItem = getRequiredItem(player).stream().findFirst().orElse(ItemStack.EMPTY); + if (requiredItem.isEmpty()) return actions; + var ctx = getPlacementContext(player); + if (ctx == null) return actions; + + ActionChain chain = new ActionChain(); + chain.addImmediateAction(new PrepareLook(ctx)); + chain.addNextTickAction(new PrepareAction(ctx)); + chain.addNextTickAction(new UseItemActionImpl(ctx)); + + actions.add(chain); + + return actions; + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/BlockIndifferentGuesserGuide.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/BlockIndifferentGuesserGuide.java new file mode 100644 index 000000000..4835f6d4c --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/BlockIndifferentGuesserGuide.java @@ -0,0 +1,54 @@ +package me.aleksilassila.litematica.printer.v1_21_4.guides.placement; + +import me.aleksilassila.litematica.printer.v1_21_4.SchematicBlockState; +import me.aleksilassila.litematica.printer.v1_21_4.config.PrinterConfig; +import net.minecraft.block.*; + +/** + * A GuesserGuide that ignores certain block state properties for specific blocks. + */ +public class BlockIndifferentGuesserGuide extends GeneralPlacementGuide { + 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 BambooShootBlock; + } + + 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); + } + + if (targetBlock instanceof MushroomBlock) { + return statesEqualIgnoreProperties(resultState, targetState, MushroomBlock.DOWN, MushroomBlock.UP, MushroomBlock.WEST, MushroomBlock.NORTH, MushroomBlock.EAST, MushroomBlock.SOUTH); + } + + if (targetBlock instanceof GlowLichenBlock && PrinterConfig.PRINTER_ALLOW_NONE_EXACT_STATES.getBooleanValue()) { + return resultState.getBlock() == targetState.getBlock(); + } + + return super.statesEqual(resultState, targetState); + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/BlockReplacementGuide.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/BlockReplacementGuide.java new file mode 100644 index 000000000..caeae57b6 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/BlockReplacementGuide.java @@ -0,0 +1,75 @@ +package me.aleksilassila.litematica.printer.v1_21_4.guides.placement; + +import me.aleksilassila.litematica.printer.v1_21_4.implementation.PrinterPlacementContext; +import me.aleksilassila.litematica.printer.v1_21_4.SchematicBlockState; +import me.aleksilassila.litematica.printer.v1_21_4.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; + +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) { + ItemStack requiredItem = getRequiredItem(player).stream().findFirst().orElse(ItemStack.EMPTY); + 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, 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_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/ChestGuide.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/ChestGuide.java new file mode 100644 index 000000000..88e636792 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/ChestGuide.java @@ -0,0 +1,92 @@ +package me.aleksilassila.litematica.printer.v1_21_4.guides.placement; + +import me.aleksilassila.litematica.printer.v1_21_4.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_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/FacingBlockGuide.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/FacingBlockGuide.java new file mode 100644 index 000000000..8901f6f05 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/FacingBlockGuide.java @@ -0,0 +1,51 @@ +package me.aleksilassila.litematica.printer.v1_21_4.guides.placement; + +import me.aleksilassila.litematica.printer.v1_21_4.SchematicBlockState; +import net.minecraft.block.*; +import net.minecraft.block.enums.BlockHalf; +import net.minecraft.state.property.Properties; +import net.minecraft.util.math.Direction; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +public class FacingBlockGuide extends SlabGuide { + public FacingBlockGuide(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); + } + if (block instanceof StairsBlock) { + Direction half = getRequiredHalf(state).getOpposite(); + return Arrays.stream(Direction.values()).filter(d -> d != half).toList(); + } + + return Arrays.stream(Direction.values()).toList(); + } + + @Override + public boolean skipOtherGuides() { + return true; + } + + private Direction getRequiredHalf(SchematicBlockState state) { + BlockState targetState = state.targetState; + BlockState currentState = state.currentState; + + if (!currentState.contains(StairsBlock.HALF)) { + return targetState.get(StairsBlock.HALF) == BlockHalf.TOP ? Direction.UP : Direction.DOWN; + } else if (currentState.get(StairsBlock.HALF) != targetState.get(StairsBlock.HALF)) { + return currentState.get(StairsBlock.HALF) == BlockHalf.TOP ? Direction.DOWN : Direction.UP; + } else { + return Direction.DOWN; + } + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/FallingBlockGuide.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/FallingBlockGuide.java new file mode 100644 index 000000000..07f4b6785 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/FallingBlockGuide.java @@ -0,0 +1,37 @@ +package me.aleksilassila.litematica.printer.v1_21_4.guides.placement; + +import me.aleksilassila.litematica.printer.v1_21_4.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 GeneralPlacementGuide { + + 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_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/FarmlandGuide.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/FarmlandGuide.java new file mode 100644 index 000000000..d46eb6c21 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/FarmlandGuide.java @@ -0,0 +1,29 @@ +package me.aleksilassila.litematica.printer.v1_21_4.guides.placement; + +import me.aleksilassila.litematica.printer.v1_21_4.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_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/FlowerPotGuide.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/FlowerPotGuide.java new file mode 100644 index 000000000..b793daf38 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/FlowerPotGuide.java @@ -0,0 +1,20 @@ +package me.aleksilassila.litematica.printer.v1_21_4.guides.placement; + +import me.aleksilassila.litematica.printer.v1_21_4.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_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/GeneralPlacementGuide.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/GeneralPlacementGuide.java new file mode 100644 index 000000000..d67fc39b6 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/GeneralPlacementGuide.java @@ -0,0 +1,433 @@ +package me.aleksilassila.litematica.printer.v1_21_4.guides.placement; + +import fi.dy.masa.litematica.world.SchematicWorldHandler; +import fi.dy.masa.litematica.world.WorldSchematic; +import me.aleksilassila.litematica.printer.v1_21_4.LitematicaMixinMod; +import me.aleksilassila.litematica.printer.v1_21_4.Printer; +import me.aleksilassila.litematica.printer.v1_21_4.SchematicBlockState; +import me.aleksilassila.litematica.printer.v1_21_4.config.PrinterConfig; +import me.aleksilassila.litematica.printer.v1_21_4.implementation.PrinterPlacementContext; +import net.minecraft.block.*; +import net.minecraft.block.enums.ChestType; +import net.minecraft.block.enums.SlabType; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.state.property.Properties; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.hit.HitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.RaycastContext; +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 { + protected static Vec3d[] extendedHitVecs = 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), + new Vec3d(-0.25, -0.49, -0.25), // 1/4 Just above the lower edge of a block. For carpets for instance as they are very thin + new Vec3d(+0.25, -0.49, -0.25), // 2/4 + new Vec3d(-0.25, -0.49, +0.25), // 3/4 + new Vec3d(+0.25, -0.49, +0.25) // 4/4 + }; + private PrinterPlacementContext contextCache = null; + + 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 Vec3d[] getPossibleHitVecs() { + return extendedHitVecs; + } + + private List getValidSides(SchematicBlockState state) { + List sides = getPossibleSides(); + + List validSides = new ArrayList<>(); + for (Direction side : sides) { + 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); + } + + return validSides; + } + + 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()); + } + + @Override + public boolean canExecute(ClientPlayerEntity player) { + if (!super.canExecute(player)) return false; + if (!PrinterConfig.STRICT_BLOCK_FACE_CHECK.getBooleanValue()) { + return true; + } + for (Direction side : getPossibleSides()) { + if (canSeeBlockFace(player, new BlockHitResult(Vec3d.ofCenter(state.blockPos), side.getOpposite(), state.blockPos.offset(side), false))) { + return true; + } + } + return false; + } + + @Override + public @Nullable PrinterPlacementContext getPlacementContext(ClientPlayerEntity player) { + if (PrinterConfig.PRINTER_AIRPLACE.getBooleanValue()) { + return getAirplaceContext(player); + } else { + return getContextByStrictLook(player); + } + } + + @Nullable + public PrinterPlacementContext getContextByStrictLook(ClientPlayerEntity player) { + if (contextCache != null && !LitematicaMixinMod.DEBUG && !PrinterConfig.NO_PLACEMENT_CACHE.getBooleanValue() && contextCache.isRaytrace == PrinterConfig.RAYCAST.getBooleanValue()) + return contextCache; + + ItemStack requiredItem = getRequiredItem(player).stream().findFirst().orElse(ItemStack.EMPTY); + int slot = getRequiredItemStackSlot(player); + + if (slot == -1) return null; + + Vec3d[] hitVecsToTryArray = getPossibleHitVecs(); + + Vec3d playerEyePos = player.getEyePos(); + // Direction relativeRotation = Direction.getEntityFacingOrder(player)[0]; + + // for (Direction lookDirection : directionsToTry) { + for (Direction side : getPossibleSides()) { + BlockPos neighborPos = state.blockPos.offset(side); + + // Check if the block face is visible. Prevents the printer from trying to place blocks on the backside of other blocks + if (PrinterConfig.STRICT_BLOCK_FACE_CHECK.getBooleanValue()) { + if (!canSeeBlockFace(player, new BlockHitResult(Vec3d.ofCenter(state.blockPos), side.getOpposite(), neighborPos, false))) { + continue; + } + } + + 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)); // Center of the block side face we are placing on + + // Now we bring on the big guns, brute force the hit vector until we find a solution that directly hits the neighbor block without obstruction + for (Vec3d hitVecToTry : hitVecsToTryArray) { + Vec3d multiplier = Vec3d.of(side.getVector()); + multiplier = new Vec3d( + multiplier.x == 0 ? 1 : 0, + multiplier.y == 0 ? 1 : 0, + multiplier.z == 0 ? 1 : 0); // Offset from the Center of the block side face we are placing on by pre calculated values. This samples different points on that face. + + Vec3d blockHit = hitVec.add(hitVecToTry.multiply(multiplier)); + Vec3d lookDirection = blockHit.subtract(playerEyePos).normalize(); + Direction relativeDirection = Direction.getFacing(lookDirection.x, lookDirection.y, lookDirection.z); + + if (playerEyePos.distanceTo(blockHit) > LitematicaMixinMod.PRINTING_RANGE.getDoubleValue()) // Check if the hit vector is in range + continue; + + if (PrinterConfig.RAYCAST.getBooleanValue() && mc.world != null && mc.player != null) { + Vec3d lookVec = blockHit.subtract(playerEyePos).normalize(); // Look vector from the player's eye to the block hit vector + Vec3d raycastEnd = playerEyePos.add(lookVec.multiply(5)); // 5 block max distance + RaycastContext raycastContext = new RaycastContext(playerEyePos, raycastEnd, RaycastContext.ShapeType.OUTLINE, RaycastContext.FluidHandling.NONE, player); + BlockHitResult result = mc.world.raycast(raycastContext); + if (result.getType() != HitResult.Type.BLOCK) { // If we didn't hit a block, skip + continue; + } + + if (result.getBlockPos().equals(neighborPos)) { + if (PrinterConfig.RAYCAST_STRICT_BLOCK_HIT.getBooleanValue()) { // Check if the right side was hit + Direction hitSide = result.getSide(); + if (hitSide.getOpposite() != side) { + continue; + } + } + if (result.getPos().distanceTo(playerEyePos) > LitematicaMixinMod.PRINTING_RANGE.getDoubleValue()) { // Check if the hit result is in range + continue; + } + BlockHitResult hitResult = new BlockHitResult(blockHit, side.getOpposite(), neighborPos, false); + PrinterPlacementContext rayTraceContext = new PrinterPlacementContext(player, hitResult, requiredItem, slot, relativeDirection, requiresShift); + rayTraceContext.canStealth = true; + rayTraceContext.isRaytrace = true; + BlockState resultState = getRequiredItemAsBlock(player) + .orElse(targetState.getBlock()) + .getPlacementState(rayTraceContext); + + if (resultState != null && correctObserverPlacement(targetState, resultState) && (statesEqual(resultState, targetState) || correctChestPlacement(targetState, resultState))) { + contextCache = rayTraceContext; + return rayTraceContext; + } + } + continue; + } else /* No Raycast */ { + BlockHitResult hitResult = new BlockHitResult(blockHit, side.getOpposite(), neighborPos, false); + PrinterPlacementContext context = new PrinterPlacementContext(player, hitResult, requiredItem, slot, relativeDirection, requiresShift); + context.canStealth = true; + 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 && correctObserverPlacement(targetState, result) && (statesEqual(result, targetState) || correctChestPlacement(targetState, result))) { + contextCache = context; + return context; + } + } + } + } + // } + + return null; + } + + protected static Direction[] directionsToTry = new Direction[]{ + Direction.NORTH, + Direction.SOUTH, + Direction.EAST, + Direction.WEST, + Direction.UP, + Direction.DOWN + }; + + /** + * Produces a PrinterPlacementContext for air placing blocks. The HitResult is brute forced by trying all sides and + * various hit positions inside the block volume. The HitResult block places against the neighbor block in the given + * side direction. To get the target block position we offset the {@link BlockHitResult#getBlockPos()} by the side + * direction. + * @param player the player + * @return the PrinterPlacementContext or null if no valid context was found + */ + @Nullable + public PrinterPlacementContext getAirplaceContext(ClientPlayerEntity player) { + if (contextCache != null && !LitematicaMixinMod.DEBUG) return contextCache; + + ItemStack requiredItem = getRequiredItem(player).stream().findFirst().orElse(ItemStack.EMPTY); + int slot = getRequiredItemStackSlot(player); + + if (slot == -1) return null; + + if (Printer.inactivityCounter <= PrinterConfig.PRINTER_MIN_INACTIVE_TIME_AIR_PLACE.getIntegerValue()) { + return null; + } + + // We'll brute force: sides (including horizontal) + hit positions inside the target block volume + // to emulate realistic clicks that yield correct orientation for special blocks (hoppers, froglights, basalt, slabs). + + // First quick attempt: original center (UP side) (fast path) + { + BlockHitResult hr = new BlockHitResult(Vec3d.ofCenter(state.blockPos), Direction.UP, state.blockPos, true); + PrinterPlacementContext quick = new PrinterPlacementContext(player, hr, requiredItem, slot, null, false); + BlockState res = getRequiredItemAsBlock(player).orElse(targetState.getBlock()).getPlacementState(quick); + if (res != null && correctObserverPlacement(targetState, res) && statesEqual(res, targetState)) { + contextCache = quick; + quick.isAirPlace = true; + return quick; + } + } + + // Hit offsets inside block relative to its min corner (0..1). Include lower y (<0.5) for bottom slabs, and center variations. + double[] ySamples = new double[]{0.25, 0.5, 0.75}; + double[] xzSamples = new double[]{0.25, 0.5, 0.75}; + + for (Direction lookDirection : directionsToTry) { + for (Direction side : getPossibleSides()) { + // For each sample point on the face or interior adjust the hit vector. + for (double y : ySamples) { + for (double x : xzSamples) { + for (double z : xzSamples) { + // Start with raw interior sample + Vec3d sample = new Vec3d(x, y, z); + Vec3d hitVec = Vec3d.of(state.blockPos).add(sample); + + // Ensure the hitVec lies on the correct face for the side: project coordinate component to face plane center + hitVec = switch (side) { + case UP -> new Vec3d(hitVec.x, state.blockPos.getY() + 1 - 1e-4, hitVec.z); + case DOWN -> new Vec3d(hitVec.x, state.blockPos.getY() + 1e-4, hitVec.z); + case NORTH -> new Vec3d(hitVec.x, hitVec.y, state.blockPos.getZ() + 1e-4); + case SOUTH -> new Vec3d(hitVec.x, hitVec.y, state.blockPos.getZ() + 1 - 1e-4); + case WEST -> new Vec3d(state.blockPos.getX() + 1e-4, hitVec.y, hitVec.z); + case EAST -> new Vec3d(state.blockPos.getX() + 1 - 1e-4, hitVec.y, hitVec.z); + }; + + BlockHitResult hitResult = new BlockHitResult(hitVec, side.getOpposite(), state.blockPos, true); + PrinterPlacementContext context = new PrinterPlacementContext(player, hitResult, requiredItem, slot, lookDirection, false); + BlockState result = getRequiredItemAsBlock(player) + .orElse(targetState.getBlock()) + .getPlacementState(context); + if (result != null && correctObserverPlacement(targetState, result) && statesEqual(result, targetState)) { + contextCache = context; + context.isAirPlace = true; + return context; + } + } + } + } + } + } + +// if (PrinterConfig.PRINTER_AIRPLACE_FLOATING_ONLY.getBooleanValue() && !isInAir(state.blockPos)) { +// return null; +// } +// if (Printer.inactivityCounter < PrinterConfig.PRINTER_MIN_INACTIVE_TIME_AIR_PLACE.getIntegerValue()) { +// return null; +// } +// if (PrinterConfig.PRINTER_AIRPLACE_RANGE.getDoubleValue() < 0 || player.getEyePos().distanceTo(Vec3d.ofCenter(state.blockPos)) > PrinterConfig.PRINTER_AIRPLACE_RANGE.getDoubleValue()) { +// return null; +// } + + return null; + } + + 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; + } + + /** + * Returns true if the observer is placed correctly or if the config printerPlaceObserversLast is set to false + * @param targetState the target state of the block being placed + * @param result the result state of the block being placed + * @return true if the observer is placed correctly, false otherwise + */ + private boolean correctObserverPlacement(BlockState targetState, BlockState result) { + if (!PrinterConfig.PRINTER_PLACE_OBSERVERS_LAST.getBooleanValue()) return true; // If the config is set to place observers last, we don't need to check this + + if (result.getBlock() != Blocks.OBSERVER) return true; + if (targetState.getBlock() != Blocks.OBSERVER) return true; + + Direction facing = result.get(Properties.FACING); + if (facing == null) return false; + + WorldSchematic schematicWorld = SchematicWorldHandler.getSchematicWorld(); + if (schematicWorld == null) return false; + if (mc.world == null) return false; + + if (schematicWorld.getBlockState(state.blockPos.offset(facing)).isAir()) { + return true; + } else { + return !mc.world.getBlockState(state.blockPos.offset(facing)).isAir(); + } + } + + protected boolean canSeeBlockFace(ClientPlayerEntity player, BlockHitResult hitResult) { + // Draw a line between the player pos and the block pos and check if the block side is visible + // BlockPos targetPos = state.blockPos; + + Direction side = hitResult.getSide(); + BlockPos blockPos = hitResult.getBlockPos(); + Vec3d playerEyePos = player.getEyePos(); + switch (side) { + case UP: + if (blockPos.getY() + 1 > playerEyePos.getY()) return false; + break; + case DOWN: + if (blockPos.getY() < playerEyePos.getY()) return false; + break; + case NORTH: + if (blockPos.getZ() < playerEyePos.getZ()) return false; + break; + case SOUTH: + if (blockPos.getZ() + 1 > playerEyePos.getZ()) return false; + break; + case EAST: + if (blockPos.getX() + 1 > playerEyePos.getX()) return false; + break; + case WEST: + if (blockPos.getX() < playerEyePos.getX()) return false; + break; + } + return true; + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/LogGuide.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/LogGuide.java new file mode 100644 index 000000000..d1d29900d --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/LogGuide.java @@ -0,0 +1,54 @@ +package me.aleksilassila.litematica.printer.v1_21_4.guides.placement; + +import me.aleksilassila.litematica.printer.v1_21_4.LitematicaMixinMod; +import me.aleksilassila.litematica.printer.v1_21_4.SchematicBlockState; +import me.aleksilassila.litematica.printer.v1_21_4.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_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/PlacementGuide.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/PlacementGuide.java new file mode 100644 index 000000000..bf80b4b4e --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/PlacementGuide.java @@ -0,0 +1,193 @@ +package me.aleksilassila.litematica.printer.v1_21_4.guides.placement; + +import me.aleksilassila.litematica.printer.v1_21_4.SchematicBlockState; +import me.aleksilassila.litematica.printer.v1_21_4.actions.*; +import me.aleksilassila.litematica.printer.v1_21_4.config.PrinterConfig; +import me.aleksilassila.litematica.printer.v1_21_4.guides.Guide; +import me.aleksilassila.litematica.printer.v1_21_4.implementation.PrinterPlacementContext; +import me.aleksilassila.litematica.printer.v1_21_4.implementation.actions.AirPlaceAction; +import me.aleksilassila.litematica.printer.v1_21_4.implementation.actions.InteractActionImpl; +import net.minecraft.block.*; +import net.minecraft.client.MinecraftClient; +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.function.BooleanBiFunction; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Box; +import net.minecraft.util.math.Direction; +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 { + MinecraftClient mc = MinecraftClient.getInstance(); + + public PlacementGuide(SchematicBlockState state) { + super(state); + } + + protected ItemStack getBlockItem(BlockState state) { + return state.getBlock().asItem().getDefaultStack(); + } + + protected Optional getRequiredItemAsBlock(ClientPlayerEntity player) { + ItemStack requiredItem = getRequiredItem(player).stream().findFirst().orElse(ItemStack.EMPTY); + + if (requiredItem.isEmpty()) { + return Optional.empty(); + } else { + if (requiredItem.getItem() instanceof BlockItem) + return Optional.of(((BlockItem) requiredItem.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; + + if (targetState.getBlock() == currentState.getBlock()) { + 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; + + BlockState resultState = getRequiredItemAsBlock(player) + .orElse(targetState.getBlock()) + .getPlacementState(ctx); + + if (resultState != null) { + if (collidesWithPlayer(resultState)) { + if (PrinterConfig.isDebug()) { + System.out.println("Block collides with player. Not placing."); + } + return false; + } + if (!resultState.canPlaceAt(state.world, state.blockPos)) return false; + return !(currentState.getBlock() instanceof FluidBlock) || canPlaceInWater(resultState); + } else { + return false; + } + + } + + public boolean isInAir(BlockPos pos) { + if (mc.world == null) return false; + for (Direction dir : Direction.values()) { + if (!mc.world.getBlockState(pos.offset(dir)).isAir()) { + return false; + } + } + return true; + } + + private boolean collidesWithPlayer(BlockState blockState) { + if (mc.player == null || mc.world == null) return true; + + VoxelShape shape = blockState.getCollisionShape(state.schematic, state.blockPos); + if (shape.isEmpty()) return false; + shape = shape.offset(state.blockPos.getX(), state.blockPos.getY(), state.blockPos.getZ()); + Box playerShape = mc.player.getBoundingBox(); + return VoxelShapes.matchesAnywhere(VoxelShapes.cuboid(playerShape), shape, BooleanBiFunction.AND); + } + + @Override + public @NotNull List execute(ClientPlayerEntity player) { + List actions = new ArrayList<>(); + PrinterPlacementContext ctx = getPlacementContext(player); + + if (ctx == null) return actions; + ActionChain actionChain = new ActionChain(); + + if (ctx.isAirPlace) { + if (ctx.lookDirection != null) { + if (PrinterConfig.PRINTER_GRIM_ROTATION.getBooleanValue()) { + actionChain.addNextTickAction(new PrepareLook(ctx)); + } else { + actionChain.addImmediateAction(new PrepareLook(ctx)); + } + } + actionChain.addNextTickAction(new PrepareAction(ctx)); + actionChain.addNextTickAction(new AirPlaceAction(ctx)); + actions.add(actionChain); + return actions; + } else { + if (PrinterConfig.PRINTER_AIRPLACE.getBooleanValue() && PrinterConfig.PRINTER_AIRPLACE_ONLY.getBooleanValue()) { + return actions; + } + } + + boolean shiftDown = mc.player.input.playerInput.sneak(); + actionChain.addImmediateAction(new PrepareLook(ctx)); + if (ctx.shouldSneak && !shiftDown) actionChain.addImmediateAction(new PresShift()); + actionChain.addNextTickAction(new PrepareAction(ctx)); + actionChain.addNextTickAction(new InteractActionImpl(ctx)); + if (ctx.shouldSneak && !shiftDown) actionChain.addNextTickAction(new ReleaseShiftAction()); + actions.add(actionChain); + + 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_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/PropertySpecificGuesserGuide.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/PropertySpecificGuesserGuide.java new file mode 100644 index 000000000..318812382 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/PropertySpecificGuesserGuide.java @@ -0,0 +1,75 @@ +package me.aleksilassila.litematica.printer.v1_21_4.guides.placement; + +import me.aleksilassila.litematica.printer.v1_21_4.SchematicBlockState; +import me.aleksilassila.litematica.printer.v1_21_4.config.PrinterConfig; +import net.minecraft.block.*; +import net.minecraft.state.property.Properties; +import net.minecraft.state.property.Property; +import java.util.stream.Stream; + +public class PropertySpecificGuesserGuide extends GeneralPlacementGuide { + protected static Property[] ignoredProperties = new Property[]{ + RepeaterBlock.DELAY, + ComparatorBlock.MODE, + ComposterBlock.LEVEL, + RedstoneWireBlock.POWER, + RedstoneWireBlock.WIRE_CONNECTION_EAST, + RedstoneWireBlock.WIRE_CONNECTION_NORTH, + RedstoneWireBlock.WIRE_CONNECTION_SOUTH, + RedstoneWireBlock.WIRE_CONNECTION_WEST, + Properties.POWERED, + Properties.TRIGGERED, + 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, + Properties.EXTENDED, + Properties.WEST_WALL_SHAPE, + Properties.EAST_WALL_SHAPE, + Properties.NORTH_WALL_SHAPE, + Properties.SOUTH_WALL_SHAPE, + Properties.ENABLED + }; + + public static Property[] rotationProperties = new Property[]{ + Properties.ROTATION, + Properties.HORIZONTAL_FACING, + Properties.AXIS, + Properties.HORIZONTAL_AXIS + }; + + public PropertySpecificGuesserGuide(SchematicBlockState state) { + super(state); + } + + @Override + protected boolean statesEqual(BlockState resultState, BlockState targetState) { + if (PrinterConfig.PRINTER_IGNORE_ROTATION.getBooleanValue()) { + // Combine rotation properties with ignored properties + return statesEqualIgnoreProperties(resultState, targetState, Stream.concat( + Stream.of(rotationProperties), + Stream.of(ignoredProperties) + ).toArray(Property[]::new)); + } + return statesEqualIgnoreProperties(resultState, targetState, ignoredProperties); + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/RailGuesserGuide.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/RailGuesserGuide.java new file mode 100644 index 000000000..255d82fcd --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/RailGuesserGuide.java @@ -0,0 +1,128 @@ +package me.aleksilassila.litematica.printer.v1_21_4.guides.placement; + +import me.aleksilassila.litematica.printer.v1_21_4.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 GeneralPlacementGuide { + 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_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/RotatingBlockGuide.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/RotatingBlockGuide.java new file mode 100644 index 000000000..fd0f97b0c --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/RotatingBlockGuide.java @@ -0,0 +1,68 @@ +package me.aleksilassila.litematica.printer.v1_21_4.guides.placement; + +import me.aleksilassila.litematica.printer.v1_21_4.config.PrinterConfig; +import me.aleksilassila.litematica.printer.v1_21_4.SchematicBlockState; +import net.minecraft.block.*; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.state.property.Properties; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.RotationCalculator; + +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 boolean canExecute(ClientPlayerEntity player) { + if (!super.canExecute(player)) return false; + + if (PrinterConfig.PRINTER_IGNORE_ROTATION.getBooleanValue()) return true; + + 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)); + + Direction targetDirection; + float g = yaw * 0.017453292F; + float j = MathHelper.sin(g); + float k = MathHelper.cos(g); + boolean bl = j > 0.0F; + boolean bl3 = k > 0.0F; + float l = bl ? j : -j; + float n = bl3 ? k : -k; + Direction direction = bl ? Direction.EAST : Direction.WEST; + Direction direction3 = bl3 ? Direction.SOUTH : Direction.NORTH; + if (l > n) { + targetDirection = direction; + } else { + targetDirection = direction3; + } + + return player.getHorizontalFacing() == targetDirection; + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/SlabGuide.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/SlabGuide.java new file mode 100644 index 000000000..ce07c02fd --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/SlabGuide.java @@ -0,0 +1,37 @@ +package me.aleksilassila.litematica.printer.v1_21_4.guides.placement; + +import me.aleksilassila.litematica.printer.v1_21_4.SchematicBlockState; +import net.minecraft.block.BlockState; +import net.minecraft.block.SlabBlock; +import net.minecraft.block.enums.SlabType; +import net.minecraft.util.math.Direction; + +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(); + } + + 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_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/TorchGuide.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/TorchGuide.java new file mode 100644 index 000000000..57117279b --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/placement/TorchGuide.java @@ -0,0 +1,34 @@ +package me.aleksilassila.litematica.printer.v1_21_4.guides.placement; + +import me.aleksilassila.litematica.printer.v1_21_4.SchematicBlockState; +import net.minecraft.block.Block; +import net.minecraft.block.HorizontalFacingBlock; +import net.minecraft.block.WallMountedBlock; +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 PropertySpecificGuesserGuide { + public TorchGuide(SchematicBlockState state) { + super(state); + } + + @Override + protected List getPossibleSides() { + // Prefer the wall-mounted FACING for wall torches; fallback to horizontal facing + Optional facing = getProperty(targetState, WallMountedBlock.FACING); + if (facing.isEmpty()) 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_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/implementation/BlockHelperImpl.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/implementation/BlockHelperImpl.java new file mode 100644 index 000000000..7c5749e32 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/implementation/BlockHelperImpl.java @@ -0,0 +1,14 @@ +package me.aleksilassila.litematica.printer.v1_21_4.implementation; + +import me.aleksilassila.litematica.printer.v1_21_4.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_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/implementation/PrinterPlacementContext.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/implementation/PrinterPlacementContext.java new file mode 100644 index 000000000..6bc0b1daa --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/implementation/PrinterPlacementContext.java @@ -0,0 +1,92 @@ +package me.aleksilassila.litematica.printer.v1_21_4.implementation; + +import me.aleksilassila.litematica.printer.v1_21_4.LitematicaMixinMod; +import me.aleksilassila.litematica.printer.v1_21_4.config.PrinterConfig; +import net.minecraft.block.BlockState; +import net.minecraft.block.FireBlock; +import net.minecraft.block.FluidBlock; +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 boolean canStealth = false; + public boolean isRaytrace = false; + public boolean isAirPlace = false; + + 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 boolean canPlace() { + if (!isAirPlace) { + return super.canPlace(); + } + if (!super.canPlace()) { + return false; + } + BlockState currentState = this.getWorld().getBlockState(hitResult.getBlockPos()); + if (this.getPlayer().getEyePos().distanceTo(hitResult.getBlockPos().toCenterPos()) > PrinterConfig.PRINTER_AIRPLACE_RANGE.getDoubleValue()) { + return false; + } + // Wrong state and not replaceable + if (!currentState.isReplaceable() && !(currentState.getBlock() instanceof FireBlock)) { + return false; + } else { + // Replaceable block. Check fluid source block replacement config + if (currentState.getBlock() instanceof FluidBlock && !LitematicaMixinMod.REPLACE_FLUIDS_SOURCE_BLOCKS.getBooleanValue()) { + // Only allow replacing fluid source blocks if the config is enabled + return false; + } + } + return true; + } + + @Override + public String toString() { + return "PrinterPlacementContext{" + + "lookDirection=" + lookDirection + + ", requiresSneaking=" + shouldSneak + + ", blockPos=" + hitResult.getBlockPos() + + ", side=" + hitResult.getSide() + +// ", hitVec=" + hitResult + + '}'; + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/implementation/actions/AirPlaceAction.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/implementation/actions/AirPlaceAction.java new file mode 100644 index 000000000..5c6a186f5 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/implementation/actions/AirPlaceAction.java @@ -0,0 +1,66 @@ +package me.aleksilassila.litematica.printer.v1_21_4.implementation.actions; + +import me.aleksilassila.litematica.printer.v1_21_4.LitematicaMixinMod; +import me.aleksilassila.litematica.printer.v1_21_4.actions.InteractAction; +import me.aleksilassila.litematica.printer.v1_21_4.implementation.PrinterPlacementContext; +import net.minecraft.block.BlockState; +import net.minecraft.block.FluidBlock; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.client.network.ClientPlayerInteractionManager; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.item.BlockItem; +import net.minecraft.network.packet.c2s.play.CloseHandledScreenC2SPacket; +import net.minecraft.network.packet.c2s.play.HandSwingC2SPacket; +import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket; +import net.minecraft.screen.slot.SlotActionType; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; + +public class AirPlaceAction extends InteractAction { + private final MinecraftClient mc = MinecraftClient.getInstance(); + public AirPlaceAction(PrinterPlacementContext context) { + super(context); + } + + @Override + protected ActionResult interact(MinecraftClient client, ClientPlayerEntity player, Hand hand, BlockHitResult hitResult) { + BlockPos pos = hitResult.isInsideBlock() ? hitResult.getBlockPos() : hitResult.getBlockPos().offset(hitResult.getSide()); + + if (!context.canPlace()) { + return ActionResult.FAIL; + } + airPlace(pos); + return ActionResult.PASS; + } + + private void airPlace(BlockPos pos) { + ClientPlayNetworkHandler connection = mc.getNetworkHandler(); + ClientPlayerInteractionManager interactionManager = mc.interactionManager; + if (mc.player == null || connection == null || interactionManager == null) return; + + if (mc.isInSingleplayer() && mc.player.getAbilities().creativeMode) { + BlockHitResult blockHitResult = new BlockHitResult(this.context.hitResult.getPos(), this.context.hitResult.getSide(), pos, true); + mc.interactionManager.interactBlock(mc.player, Hand.MAIN_HAND, blockHitResult); + mc.player.swingHand(Hand.MAIN_HAND, false); + return; + } + + if (mc.player.getInventory().getStack(PlayerInventory.OFF_HAND_SLOT).getItem() instanceof BlockItem) { + mc.interactionManager.clickSlot(0, 45, 0, SlotActionType.PICKUP, mc.player); + mc.getNetworkHandler().sendPacket(new CloseHandledScreenC2SPacket(mc.player.playerScreenHandler.syncId)); + } + connection.sendPacket(new PlayerActionC2SPacket(PlayerActionC2SPacket.Action.SWAP_ITEM_WITH_OFFHAND, BlockPos.ORIGIN, Direction.DOWN)); + + Hand hand = Hand.OFF_HAND; + BlockHitResult blockHitResult = new BlockHitResult(this.context.hitResult.getPos(), this.context.hitResult.getSide(), pos, true); + interactionManager.interactBlock(mc.player, hand, blockHitResult); + mc.player.swingHand(Hand.MAIN_HAND, false); + connection.sendPacket(new HandSwingC2SPacket(hand)); + connection.sendPacket(new PlayerActionC2SPacket(PlayerActionC2SPacket.Action.SWAP_ITEM_WITH_OFFHAND, BlockPos.ORIGIN, Direction.DOWN)); + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/implementation/actions/InteractActionImpl.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/implementation/actions/InteractActionImpl.java new file mode 100644 index 000000000..7324d75cc --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/implementation/actions/InteractActionImpl.java @@ -0,0 +1,26 @@ +package me.aleksilassila.litematica.printer.v1_21_4.implementation.actions; + +import me.aleksilassila.litematica.printer.v1_21_4.actions.InteractAction; +import me.aleksilassila.litematica.printer.v1_21_4.config.PrinterConfig; +import me.aleksilassila.litematica.printer.v1_21_4.implementation.PrinterPlacementContext; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.hit.BlockHitResult; + +public class InteractActionImpl extends InteractAction { + public InteractActionImpl(PrinterPlacementContext context) { + super(context); + } + private final MinecraftClient mc = MinecraftClient.getInstance(); + @Override + protected ActionResult interact(MinecraftClient client, ClientPlayerEntity player, Hand hand, BlockHitResult hitResult) { + ActionResult result = client.interactionManager.interactBlock(player, hand, hitResult); + if (!result.isAccepted()) { + if (PrinterConfig.PRINTER_DEBUG_LOG.getBooleanValue()) System.out.println("Failed to interact with block got " + result); + } + mc.player.swingHand(Hand.MAIN_HAND); + return result; + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/implementation/actions/UseItemActionImpl.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/implementation/actions/UseItemActionImpl.java new file mode 100644 index 000000000..e838fbeab --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/implementation/actions/UseItemActionImpl.java @@ -0,0 +1,31 @@ +package me.aleksilassila.litematica.printer.v1_21_4.implementation.actions; + + +import me.aleksilassila.litematica.printer.v1_21_4.actions.InteractAction; +import me.aleksilassila.litematica.printer.v1_21_4.config.PrinterConfig; +import me.aleksilassila.litematica.printer.v1_21_4.implementation.PrinterPlacementContext; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.hit.BlockHitResult; + +/* + * @author IceTank + * @since 08.03.2026 + */ +public class UseItemActionImpl extends InteractAction { + public UseItemActionImpl(PrinterPlacementContext context) { + super(context); + } + private final MinecraftClient mc = MinecraftClient.getInstance(); + @Override + protected ActionResult interact(MinecraftClient client, ClientPlayerEntity player, Hand hand, BlockHitResult hitResult) { + ActionResult result = client.interactionManager.interactItem(player, hand); + if (!result.isAccepted()) { + if (PrinterConfig.PRINTER_DEBUG_LOG.getBooleanValue()) System.out.println("Failed to interact with block got " + result); + } + mc.player.swingHand(Hand.MAIN_HAND); + return result; + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/implementation/mixin/MixinClientPlayerEntity.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/implementation/mixin/MixinClientPlayerEntity.java new file mode 100644 index 000000000..da7390589 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/implementation/mixin/MixinClientPlayerEntity.java @@ -0,0 +1,109 @@ +package me.aleksilassila.litematica.printer.v1_21_4.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_21_4.LitematicaMixinMod; +import me.aleksilassila.litematica.printer.v1_21_4.Printer; +import me.aleksilassila.litematica.printer.v1_21_4.SchematicBlockState; +import me.aleksilassila.litematica.printer.v1_21_4.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) { + if (!didCheckForUpdates) { + didCheckForUpdates = true; + + checkForUpdates(); + } + + if (LitematicaMixinMod.freeLook.getPrevPerspective() == null) { + LitematicaMixinMod.freeLook.setPrevPerspective(client.options.getPerspective()); + } + if (LitematicaMixinMod.printer != null) { +// LitematicaMixinMod.printer.actionHandler.onPostTick(); + } + } + + @Inject(at = @At("HEAD"), method = "tick") + public void tickHead(CallbackInfo ci) { + ClientPlayerEntity clientPlayer = (ClientPlayerEntity) (Object) this; + if (LitematicaMixinMod.printer == null || LitematicaMixinMod.printer.player != clientPlayer) { + System.out.println("Initializing printer, player: " + clientPlayer + ", client: " + client); + LitematicaMixinMod.printer = new Printer(client, clientPlayer); + } + LitematicaMixinMod.printer.actionHandler.processPreviousTickActions(); + LitematicaMixinMod.printer.onGameTick(); + LitematicaMixinMod.printer.actionHandler.processCurrentTickActions(); + Printer.inventoryManager.tick(); + LitematicaMixinMod.freeLook.onGameTick(); + LitematicaMixinMod.movementHandler.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_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/AbstractBlockInvoker.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/AbstractBlockInvoker.java new file mode 100644 index 000000000..724848ab3 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/AbstractBlockInvoker.java @@ -0,0 +1,21 @@ +package me.aleksilassila.litematica.printer.v1_21_4.mixin; + +/* + * @author IceTank + * @since 17.03.2025 + */ + +import net.minecraft.block.AbstractBlock; +import net.minecraft.block.BlockState; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.WorldView; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(AbstractBlock.class) +public interface AbstractBlockInvoker { + @Invoker("getPickStack") + ItemStack getPickStack(WorldView world, BlockPos pos, BlockState state, boolean includeData); +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/AxeItemAccessor.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/AxeItemAccessor.java new file mode 100644 index 000000000..0ab009401 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/AxeItemAccessor.java @@ -0,0 +1,20 @@ +package me.aleksilassila.litematica.printer.v1_21_4.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_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/ButtonListenerMixin.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/ButtonListenerMixin.java new file mode 100644 index 000000000..f4a673dcc --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/ButtonListenerMixin.java @@ -0,0 +1,41 @@ +package me.aleksilassila.litematica.printer.v1_21_4.mixin; + +import com.llamalad7.mixinextras.sugar.Local; +import com.llamalad7.mixinextras.sugar.ref.LocalBooleanRef; +import com.llamalad7.mixinextras.sugar.ref.LocalRef; +import fi.dy.masa.litematica.schematic.LitematicaSchematic; +import fi.dy.masa.malilib.gui.Message; +import fi.dy.masa.malilib.gui.button.ButtonBase; +import fi.dy.masa.malilib.gui.widgets.WidgetFileBrowserBase; +import fi.dy.masa.malilib.util.InfoUtils; +import me.aleksilassila.litematica.printer.v1_21_4.SchematicConverter; +import me.aleksilassila.litematica.printer.v1_21_4.config.PrinterConfig; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +/** + * @author IceTank + * @since 17.12.2024 + */ +@Mixin(targets = "fi.dy.masa.litematica.gui.GuiSchematicLoad$ButtonListener", remap = false) +public class ButtonListenerMixin { + + @Inject(method = "actionPerformedWithButton", + at = @At( + target = "Lfi/dy/masa/litematica/data/SchematicHolder;addSchematic(Lfi/dy/masa/litematica/schematic/LitematicaSchematic;Z)V", + value = "INVOKE", shift = At.Shift.BEFORE) + ) + private void onActionPerformedWithButton(ButtonBase button, int mouseButton, CallbackInfo ci, + @Local LocalBooleanRef warnType, @Local LocalRef schematic, + @Local WidgetFileBrowserBase.DirectoryEntry entry) { + if (!warnType.get() || !PrinterConfig.AUTO_CONVERT_SCHEMATIC_TO_LITEMATIC_ON_LOAD.getBooleanValue()) { + return; + } + LitematicaSchematic newSchem = SchematicConverter.convertAndReturn(schematic.get().getFile(), entry.getDirectory()); + warnType.set(false); + schematic.set(newSchem); + InfoUtils.showGuiOrInGameMessage(Message.MessageType.INFO, 15000, "Auto converted schematic to litematic format"); + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/ConfigsMixin.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/ConfigsMixin.java new file mode 100644 index 000000000..4a2fce2bc --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/ConfigsMixin.java @@ -0,0 +1,43 @@ +package me.aleksilassila.litematica.printer.v1_21_4.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_21_4.LitematicaMixinMod; +import me.aleksilassila.litematica.printer.v1_21_4.config.PrinterConfig; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +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(); + } + + @Inject(method = "loadFromFile", at = @At("RETURN")) + private static void loadFromFilePost(CallbackInfo ci) { + PrinterConfig.onConfigFileLoad(); + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/GuiConfigsMixin.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/GuiConfigsMixin.java new file mode 100644 index 000000000..f3251f46b --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/GuiConfigsMixin.java @@ -0,0 +1,61 @@ +package me.aleksilassila.litematica.printer.v1_21_4.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_21_4.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_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/InputHandlerMixin.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/InputHandlerMixin.java new file mode 100644 index 000000000..e59f708c3 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/InputHandlerMixin.java @@ -0,0 +1,25 @@ +package me.aleksilassila.litematica.printer.v1_21_4.mixin; + +import fi.dy.masa.litematica.event.InputHandler; +import fi.dy.masa.malilib.config.options.ConfigHotkey; +import me.aleksilassila.litematica.printer.v1_21_4.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_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/LitematicaSchematicAccessor.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/LitematicaSchematicAccessor.java new file mode 100644 index 000000000..abd189984 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/LitematicaSchematicAccessor.java @@ -0,0 +1,20 @@ +package me.aleksilassila.litematica.printer.v1_21_4.mixin; + +import fi.dy.masa.litematica.schematic.LitematicaSchematic; +import fi.dy.masa.litematica.util.FileType; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +import java.io.File; + +/** + * @author IceTank + * @since 17.12.2024 + */ +@Mixin(LitematicaSchematic.class) +public interface LitematicaSchematicAccessor { + @Invoker("") + static LitematicaSchematic invokeConstructor(File file, FileType fileType) { + throw new AssertionError(); + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinAccessorClientPlayerEntity.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinAccessorClientPlayerEntity.java new file mode 100644 index 000000000..e5da28b2e --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinAccessorClientPlayerEntity.java @@ -0,0 +1,27 @@ +package me.aleksilassila.litematica.printer.v1_21_4.mixin; + +import net.minecraft.client.network.ClientPlayerEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(ClientPlayerEntity.class) +public interface MixinAccessorClientPlayerEntity { + @Accessor("ticksLeftToDoubleTapSprint") + void setTicksLeftToDoubleTapSprint(int ticksLeftToDoubleTapSprint); + + @Accessor("lastSneaking") + boolean getLastSneaking(); + + @Accessor("lastSneaking") + void setLastSneaking(boolean lastSneaking); + + @Accessor("lastPitch") + float getLastPitch(); + @Accessor("lastPitch") + void setLastPitch(float lastPitch); + + @Accessor("lastYaw") + float getLastYaw(); + @Accessor("lastYaw") + void setLastYaw(float lastYaw); +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinAccessorKeyBinding.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinAccessorKeyBinding.java new file mode 100644 index 000000000..5fdeae18b --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinAccessorKeyBinding.java @@ -0,0 +1,12 @@ +package me.aleksilassila.litematica.printer.v1_21_4.mixin; + +import net.minecraft.client.option.KeyBinding; +import net.minecraft.client.util.InputUtil; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(KeyBinding.class) +public interface MixinAccessorKeyBinding { + @Accessor("boundKey") + InputUtil.Key getBoundKey(); +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinCamera.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinCamera.java new file mode 100644 index 000000000..1e0e84f6d --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinCamera.java @@ -0,0 +1,22 @@ +package me.aleksilassila.litematica.printer.v1_21_4.mixin; + +import me.aleksilassila.litematica.printer.v1_21_4.FreeLook; +import me.aleksilassila.litematica.printer.v1_21_4.LitematicaMixinMod; +import net.minecraft.client.render.Camera; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArgs; +import org.spongepowered.asm.mixin.injection.invoke.arg.Args; + +@Mixin(Camera.class) +public class MixinCamera { + @ModifyArgs(method = "update", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/Camera;setRotation(FF)V")) + private void onUpdateSetRotationArgs(Args args) { + FreeLook freeLook = LitematicaMixinMod.freeLook; + + if (freeLook.isEnabled()) { + args.set(0, freeLook.getCameraYaw()); + args.set(1, freeLook.getCameraPitch()); + } + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinClientConnection.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinClientConnection.java new file mode 100644 index 000000000..6622642f1 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinClientConnection.java @@ -0,0 +1,62 @@ +package me.aleksilassila.litematica.printer.v1_21_4.mixin; + +import io.netty.channel.ChannelHandlerContext; +import me.aleksilassila.litematica.printer.v1_21_4.LitematicaMixinMod; +import me.aleksilassila.litematica.printer.v1_21_4.Printer; +import me.aleksilassila.litematica.printer.v1_21_4.config.PrinterConfig; +import net.minecraft.client.MinecraftClient; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.item.BlockItem; +import net.minecraft.network.ClientConnection; +import net.minecraft.network.packet.Packet; +import net.minecraft.network.packet.s2c.play.ScreenHandlerSlotUpdateS2CPacket; +import net.minecraft.screen.PlayerScreenHandler; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ClientConnection.class) +public class MixinClientConnection { + @Unique + private static final MinecraftClient mc = MinecraftClient.getInstance(); + + @Inject(method = "channelRead0*", at = @At("HEAD"), cancellable = true) + private void channelReadPre(ChannelHandlerContext channelHandlerContext, Packet packet, CallbackInfo callback) { + if (!LitematicaMixinMod.PRINT_MODE.getBooleanValue() && !LitematicaMixinMod.PRINT.getKeybind().isPressed()) { + return; + } + if (Printer.inactivityCounter > 20) { + return; + } + if (PrinterConfig.PRINTER_AIRPLACE.getBooleanValue() && PrinterConfig.PRINTER_AIRPLACE_OFFHAND_SLOT_SUPPRESS.getBooleanValue()) { + if (packet instanceof ScreenHandlerSlotUpdateS2CPacket packet1) { + if (packet1.getSyncId() == -2 && packet1.getSlot() == PlayerInventory.OFF_HAND_SLOT) { + callback.cancel(); + } else if (packet1.getSyncId() == 0 && PlayerScreenHandler.isInHotbar(packet1.getSyncId())) { + if (packet1.getSlot() == PlayerScreenHandler.OFFHAND_ID) { + callback.cancel(); + } + } + } + } + // This prevents the server from sending the client the packets that would normally be sent to the client + // Just wanted to keep this comment above because copilot wrote it. What does that even mean? + // This prevents 2b from fucking up your inventory with useless slot update packets. Turns out 2b sends slot update + // packet AND an entire inventory update packet just because. The client does not like that so you get a shit ton of ghost items. + if (PrinterConfig.PRINTER_SUPER_CHINESE_GHOST_ITEM_FIX.getBooleanValue()) { + if (packet instanceof ScreenHandlerSlotUpdateS2CPacket packet1) { + // Looks like faulty packet is always on syncId = 0 + if (packet1.getSyncId() == 0 && mc.player != null) { + if (packet1.getSlot() >= PlayerScreenHandler.HOTBAR_START || packet1.getSlot() < PlayerScreenHandler.HOTBAR_END) { + // Only cancel updates to block items. Some blocks might not be in the USABLE_SLOTS list but still get used for placing + if (mc.player.playerScreenHandler.getSlot(packet1.getSlot()).getStack().getItem() instanceof BlockItem) { + callback.cancel(); + } + } + } + } + } + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinClientPlayerEntity.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinClientPlayerEntity.java new file mode 100644 index 000000000..998ee467e --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinClientPlayerEntity.java @@ -0,0 +1,19 @@ +package me.aleksilassila.litematica.printer.v1_21_4.mixin; + +import me.aleksilassila.litematica.printer.v1_21_4.config.PrinterConfig; +import net.minecraft.client.network.ClientPlayerEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ClientPlayerEntity.class) +public class MixinClientPlayerEntity { + @Inject(method = "tickMovement", at = @At("HEAD")) + public void tickMovement(CallbackInfo ci) { + MixinClientPlayerEntity clientPlayer = this; + if (PrinterConfig.PREVENT_DOUBLE_TAP_SPRINTING.getBooleanValue()) { + ((MixinAccessorClientPlayerEntity) clientPlayer).setTicksLeftToDoubleTapSprint(0); + } + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinMouse.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinMouse.java new file mode 100644 index 000000000..012115f76 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinMouse.java @@ -0,0 +1,57 @@ +package me.aleksilassila.litematica.printer.v1_21_4.mixin; + +import me.aleksilassila.litematica.printer.v1_21_4.FreeLook; +import me.aleksilassila.litematica.printer.v1_21_4.LitematicaMixinMod; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.Mouse; +import net.minecraft.util.math.MathHelper; +import org.spongepowered.asm.mixin.Final; +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; + +@Mixin(value = Mouse.class) +public class MixinMouse { + @Shadow private double cursorDeltaX; + @Shadow private double cursorDeltaY; + + @Shadow @Final private MinecraftClient client; + @Inject(method = "updateMouse", + at = @At(value = "INVOKE", + target = "Lnet/minecraft/client/MinecraftClient;getTutorialManager()Lnet/minecraft/client/tutorial/TutorialManager;", + shift = At.Shift.BEFORE + ), cancellable = true + ) + private void updateMouseChangeLookDirection(CallbackInfo ci) { + FreeLook freeLook = LitematicaMixinMod.freeLook; + if (freeLook.isEnabled()) { + double f = this.client.options.getMouseSensitivity().getValue() * 0.6000000238418579 + 0.20000000298023224; + double g = f * f * f; + double h = g * 8.0; + double k = this.cursorDeltaX * h; + double l = this.cursorDeltaY * h; + int m = 1; + if (this.client.options.getInvertYMouse().getValue()) { + m = -1; + } + float yaw = (float) (freeLook.getCameraYaw() + k * 0.15F); + freeLook.setCameraYaw(yaw); + float pitch = MathHelper.clamp((float) (freeLook.getCameraPitch() + (l * (double) m) * 0.15F), -90.0F, 90.0F); + freeLook.setCameraPitch(pitch); + if (Math.abs(pitch) > 90.0F) { + yaw = pitch > 0.0F ? 90.0F : -90.0F; + freeLook.setCameraYaw(yaw); + } + cursorDeltaX = 0.0; + cursorDeltaY = 0.0; + if (freeLook.shouldRotate()) { + if (this.client.player != null) { + this.client.player.changeLookDirection(k, l * (double)m); + } + } + ci.cancel(); + } + } +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/PlayerMoveC2SPacketMixin.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/PlayerMoveC2SPacketMixin.java new file mode 100644 index 000000000..96f394145 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/PlayerMoveC2SPacketMixin.java @@ -0,0 +1,36 @@ +package me.aleksilassila.litematica.printer.v1_21_4.mixin; + +import me.aleksilassila.litematica.printer.v1_21_4.LitematicaMixinMod; +import me.aleksilassila.litematica.printer.v1_21_4.Printer; +import me.aleksilassila.litematica.printer.v1_21_4.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 = "", at = @At("HEAD"), ordinal = 0, argsOnly = true) + 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 = "", at = @At("HEAD"), ordinal = 1, argsOnly = true) + 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_21_11/src/main/resources/assets/modid/icon.png b/v1_21_11/src/main/resources/assets/modid/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8a087cc63135355ac4274130e24e117b082e4e03 GIT binary patch literal 25538 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 V-D4KzBa8q5002ovPDHLkV1gF~YRdos literal 0 HcmV?d00001 diff --git a/v1_21_11/src/main/resources/fabric.mod.json b/v1_21_11/src/main/resources/fabric.mod.json new file mode 100644 index 000000000..411f0f947 --- /dev/null +++ b/v1_21_11/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_21_4.LitematicaMixinMod" + ] + }, + "mixins": [ + "litematica-printer.mixins.json", + "litematica-printer-implementation.mixins.json" + ], + "depends": { + "fabricloader": ">=0.18.1", + "fabric": "*", + "minecraft": "~1.21.11", + "java": ">=21" + }, + "custom": { + "modmenu": { + "parent": "carpet" + } + } +} + diff --git a/v1_21_11/src/main/resources/litematica-printer-implementation.mixins.json b/v1_21_11/src/main/resources/litematica-printer-implementation.mixins.json new file mode 100644 index 000000000..3262866da --- /dev/null +++ b/v1_21_11/src/main/resources/litematica-printer-implementation.mixins.json @@ -0,0 +1,14 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "me.aleksilassila.litematica.printer.v1_21_4.implementation.mixin", + "compatibilityLevel": "JAVA_16", + "mixins": [ + ], + "client": [ + "MixinClientPlayerEntity" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/v1_21_11/src/main/resources/litematica-printer.mixins.json b/v1_21_11/src/main/resources/litematica-printer.mixins.json new file mode 100644 index 000000000..8d1526eec --- /dev/null +++ b/v1_21_11/src/main/resources/litematica-printer.mixins.json @@ -0,0 +1,26 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "me.aleksilassila.litematica.printer.v1_21_4.mixin", + "compatibilityLevel": "JAVA_16", + "mixins": [ + "LitematicaSchematicAccessor", + "MixinClientConnection" + ], + "client": [ + "AxeItemAccessor", + "ButtonListenerMixin", + "ConfigsMixin", + "GuiConfigsMixin", + "InputHandlerMixin", + "MixinAccessorClientPlayerEntity", + "MixinAccessorKeyBinding", + "MixinCamera", + "MixinClientPlayerEntity", + "MixinMouse", + "PlayerMoveC2SPacketMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} From 1c7afcd0298fce278c8563ce46f37f7590b9ef57 Mon Sep 17 00:00:00 2001 From: ejeebus Date: Sat, 6 Jun 2026 09:47:40 +0000 Subject: [PATCH 02/14] =?UTF-8?q?Fix=20MC=201.21.4=E2=86=921.21.11=20API?= =?UTF-8?q?=20incompatibilities?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename ClientCommandC2SPacket.Mode PRESS/RELEASE_SHIFT_KEY → START/STOP_SNEAKING - Replace PlayerInventory.main direct field access with getStack/setStack/36 - Replace inventory.selectedSlot writes with PlayerInventoryAccessor mixin - Replace inventory.selectedSlot reads with getSelectedSlot() - Fix InputUtil.isKeyPressed to take Window instead of long handle - Fix SchematicConverter.writeToFile to use Path (out.toPath()) - Fix ButtonListenerMixin to call getFile().toFile() for File param - Fix MixinMouse to use options.invertYMouse directly (getter removed) - Add PlayerInventoryAccessor mixin for selectedSlot write access https://claude.ai/code/session_012vAEEvZwTHLuks9BMc13yB --- .../printer/v1_21_4/InventoryManager.java | 42 ++++++++++--------- .../printer/v1_21_4/MovementHandler.java | 2 +- .../printer/v1_21_4/SchematicConverter.java | 2 +- .../printer/v1_21_4/actions/PresShift.java | 2 +- .../v1_21_4/actions/ReleaseShiftAction.java | 2 +- .../printer/v1_21_4/guides/Guide.java | 8 ++-- .../v1_21_4/mixin/ButtonListenerMixin.java | 2 +- .../printer/v1_21_4/mixin/MixinMouse.java | 2 +- .../mixin/PlayerInventoryAccessor.java | 11 +++++ .../resources/litematica-printer.mixins.json | 1 + 10 files changed, 44 insertions(+), 30 deletions(-) create mode 100644 v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/PlayerInventoryAccessor.java diff --git a/v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/InventoryManager.java b/v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/InventoryManager.java index 4be0539c7..a9a227786 100644 --- a/v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/InventoryManager.java +++ b/v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/InventoryManager.java @@ -108,7 +108,7 @@ private boolean swapToHotbar(ClientPlayerEntity player, Item item) { } hotbarSlots.get(nextSlot).addTicksLocked(10); hotbarSlots.get(nextSlot).waitingForItem = item; - player.getInventory().selectedSlot = nextSlot; + ((me.aleksilassila.litematica.printer.v1_21_4.mixin.PlayerInventoryAccessor) player.getInventory()).setSelectedSlot(nextSlot); if (PrinterConfig.PRINTER_DEBUG_LOG.getBooleanValue()) { System.out.println("Swapping item from inventory: " + slot + " into hotbar -> " + nextSlot); } @@ -127,7 +127,7 @@ public void pickSlot(WorldSchematic world, ClientPlayerEntity player, BlockPos p int slot = inv.getSlotWithStack(stack); boolean shouldPick = slot > 8; if (slot != -1 && !shouldPick) { - player.getInventory().selectedSlot = slot; + ((me.aleksilassila.litematica.printer.v1_21_4.mixin.PlayerInventoryAccessor) player.getInventory()).setSelectedSlot(slot); } else if (slot != -1) { InventoryUtils.setPickedItemToHand(slot, stack, mc); // https://github.com/sakura-ryoko/litematica-printer/blob/f8e38a2b31708e61f8a5fad0f2989d6834495da4/src/main/java/me/aleksilassila/litematica/printer/actions/PrepareAction.java#L71 } else if (Configs.Generic.PICK_BLOCK_SHULKERS.getBooleanValue()) { @@ -136,7 +136,7 @@ public void pickSlot(WorldSchematic world, ClientPlayerEntity player, BlockPos p if (slot > 8) { InventoryUtils.setPickedItemToHand(slot, stack, mc); } else { - inv.selectedSlot = slot; + ((me.aleksilassila.litematica.printer.v1_21_4.mixin.PlayerInventoryAccessor) inv).setSelectedSlot(slot); } } } @@ -154,7 +154,7 @@ public static int findSlotWithBoxWithItem(PlayerInventory inventory, ItemStack s int bestCount = lestFirst ? Integer.MAX_VALUE : 0; int bestSlot = -1; - for (int slotNum = 0; slotNum < inventory.main.size(); slotNum += 1) { + for (int slotNum = 0; slotNum < 36; slotNum += 1) { ItemStack itemStack = inventory.getStack(slotNum); int count = shulkerBoxItemCount(itemStack, stackReference); if (lestFirst && count < bestCount && count > 0) { @@ -197,8 +197,8 @@ public boolean select(ItemStack itemStack) { if (player.getAbilities().creativeMode) { depositCursorStack(); this.addPickBlock(inventory, itemStack); - mc.interactionManager.clickCreativeStack(player.getStackInHand(Hand.MAIN_HAND), 36 + inventory.selectedSlot); - updateLastUsedSlot(inventory.selectedSlot); + mc.interactionManager.clickCreativeStack(player.getStackInHand(Hand.MAIN_HAND), 36 + inventory.getSelectedSlot()); + updateLastUsedSlot(inventory.getSelectedSlot()); return true; } else { int hotbarSlot = getHotbarSlotWithItem(player, itemStack); @@ -214,9 +214,9 @@ public boolean select(ItemStack itemStack) { if (hotbarSlot == -1) { return false; } - if (hotbarSlot != player.getInventory().selectedSlot) { + if (hotbarSlot != player.getInventory().getSelectedSlot()) { player.networkHandler.sendPacket(new UpdateSelectedSlotC2SPacket(hotbarSlot)); - player.getInventory().selectedSlot = hotbarSlot; + ((me.aleksilassila.litematica.printer.v1_21_4.mixin.PlayerInventoryAccessor) player.getInventory()).setSelectedSlot(hotbarSlot); } updateLastUsedSlot(hotbarSlot); return false; @@ -225,9 +225,9 @@ public boolean select(ItemStack itemStack) { } else { depositCursorStack(); // Switch to hotbar slot - if (hotbarSlot != player.getInventory().selectedSlot) { + if (hotbarSlot != player.getInventory().getSelectedSlot()) { player.networkHandler.sendPacket(new UpdateSelectedSlotC2SPacket(hotbarSlot)); - player.getInventory().selectedSlot = hotbarSlot; + ((me.aleksilassila.litematica.printer.v1_21_4.mixin.PlayerInventoryAccessor) player.getInventory()).setSelectedSlot(hotbarSlot); } updateLastUsedSlot(hotbarSlot); return true; @@ -239,22 +239,24 @@ public boolean select(ItemStack itemStack) { // https://github.com/sakura-ryoko/litematica-printer/blob/f8e38a2b31708e61f8a5fad0f2989d6834495da4/src/main/java/me/aleksilassila/litematica/printer/actions/PrepareAction.java#L95 private void addPickBlock(PlayerInventory inv, ItemStack stack) { + me.aleksilassila.litematica.printer.v1_21_4.mixin.PlayerInventoryAccessor invAccessor = + (me.aleksilassila.litematica.printer.v1_21_4.mixin.PlayerInventoryAccessor) inv; int slot = inv.getSlotWithStack(stack); if (slot >= 0 && slot < 9) { - inv.selectedSlot = slot; + invAccessor.setSelectedSlot(slot); } else { if (slot == -1) { - inv.selectedSlot = inv.getSwappableHotbarSlot(); + invAccessor.setSelectedSlot(inv.getSwappableHotbarSlot()); - if (!inv.main.get(inv.selectedSlot).isEmpty()) { + if (!inv.getStack(inv.getSelectedSlot()).isEmpty()) { int empty = inv.getEmptySlot(); if (empty != -1) { - inv.main.set(empty, inv.main.get(inv.selectedSlot)); + inv.setStack(empty, inv.getStack(inv.getSelectedSlot())); } } - inv.main.set(inv.selectedSlot, stack); + inv.setStack(inv.getSelectedSlot(), stack); } else { inv.swapSlotWithHotbar(slot); } @@ -327,10 +329,10 @@ private static int getBestInventorySlotWithItem(ClientPlayerEntity player, ItemS int lowestCount = 0; int lowestSlot = -1; - for (int i = 9; i < inventory.main.size(); ++i) { - if (!(inventory.main.get(i)).isEmpty() && ItemStack.areItemsAndComponentsEqual(itemStack, inventory.main.get(i))) { - if (inventory.main.get(i).getCount() < lowestCount || lowestSlot == -1) { - lowestCount = inventory.main.get(i).getCount(); + for (int i = 9; i < 36; ++i) { + if (!inventory.getStack(i).isEmpty() && ItemStack.areItemsAndComponentsEqual(itemStack, inventory.getStack(i))) { + if (inventory.getStack(i).getCount() < lowestCount || lowestSlot == -1) { + lowestCount = inventory.getStack(i).getCount(); lowestSlot = i; } } @@ -348,7 +350,7 @@ public int getHotbarSlotWithItem(ClientPlayerEntity player, ItemStack itemStack) if (itemStack.isEmpty()) return -1; for (int i = 0; i < 9; ++i) { - if (!inventory.main.get(i).isEmpty() && ItemStack.areItemsEqual(inventory.main.get(i), itemStack)) { + if (!inventory.getStack(i).isEmpty() && ItemStack.areItemsEqual(inventory.getStack(i), itemStack)) { // if (hotbarSlots.get(i).ticksLocked == 0) { return i; // } diff --git a/v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/MovementHandler.java b/v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/MovementHandler.java index 9bc7c3fc1..f14392988 100644 --- a/v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/MovementHandler.java +++ b/v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/MovementHandler.java @@ -207,6 +207,6 @@ static void apply(InputDirections direction) { } static boolean isKeyPressed(KeyBinding keyBinding) { - return InputUtil.isKeyPressed(MinecraftClient.getInstance().getWindow().getHandle(), ((MixinAccessorKeyBinding) keyBinding).getBoundKey().getCode()); + return InputUtil.isKeyPressed(MinecraftClient.getInstance().getWindow(), ((MixinAccessorKeyBinding) keyBinding).getBoundKey().getCode()); } } diff --git a/v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/SchematicConverter.java b/v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/SchematicConverter.java index c86c11dab..a99759a3c 100644 --- a/v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/SchematicConverter.java +++ b/v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/SchematicConverter.java @@ -15,7 +15,7 @@ public static LitematicaSchematic convertAndReturn(File file, File out) { LitematicaSchematic schematic = LitematicaSchematicAccessor.invokeConstructor(file, FileType.VANILLA_STRUCTURE); schematic.readFromFile(); String fileName = file.getName().replace(".nbt", ""); - schematic.writeToFile(out, fileName, true); + schematic.writeToFile(out.toPath(), fileName, true); LitematicaSchematic newSchem = LitematicaSchematicAccessor.invokeConstructor(new File(out, fileName + ".litematic"), FileType.LITEMATICA_SCHEMATIC); newSchem.readFromFile(); return newSchem; diff --git a/v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/PresShift.java b/v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/PresShift.java index 84a7a456c..1cac5ed79 100644 --- a/v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/PresShift.java +++ b/v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/PresShift.java @@ -11,7 +11,7 @@ public boolean send(MinecraftClient client, ClientPlayerEntity player) { player.input.playerInput = new PlayerInput(player.input.playerInput.forward(), player.input.playerInput.backward(), player.input.playerInput.left(), player.input.playerInput.right(), player.input.playerInput.jump(), true, player.input.playerInput.sprint()); - player.networkHandler.sendPacket(new ClientCommandC2SPacket(player, ClientCommandC2SPacket.Mode.PRESS_SHIFT_KEY)); + player.networkHandler.sendPacket(new ClientCommandC2SPacket(player, ClientCommandC2SPacket.Mode.START_SNEAKING)); return true; } } diff --git a/v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/ReleaseShiftAction.java b/v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/ReleaseShiftAction.java index 164ec3906..f4ae8b49b 100644 --- a/v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/ReleaseShiftAction.java +++ b/v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/ReleaseShiftAction.java @@ -13,7 +13,7 @@ public boolean send(MinecraftClient client, ClientPlayerEntity player) { player.input.playerInput.backward(), player.input.playerInput.left(), player.input.playerInput.right(), player.input.playerInput.jump(), mc.options.sneakKey.isPressed(), player.input.playerInput.sprint()); if (!mc.options.sneakKey.isPressed()) { - player.networkHandler.sendPacket(new ClientCommandC2SPacket(player, ClientCommandC2SPacket.Mode.RELEASE_SHIFT_KEY)); + player.networkHandler.sendPacket(new ClientCommandC2SPacket(player, ClientCommandC2SPacket.Mode.STOP_SNEAKING)); } return true; } diff --git a/v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/Guide.java b/v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/Guide.java index 00d40aed3..3b4441c84 100644 --- a/v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/Guide.java +++ b/v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/Guide.java @@ -36,9 +36,9 @@ protected boolean playerHasRightItem(ClientPlayerEntity player) { public 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)) { + for (int i = 0; i < 36; ++i) { + if (itemStack.isEmpty() && inventory.getStack(i).isOf(itemStack.getItem())) return i; + if (!inventory.getStack(i).isEmpty() && ItemStack.areItemsEqual(inventory.getStack(i), itemStack)) { return i; } } @@ -48,7 +48,7 @@ public int getSlotWithItem(ClientPlayerEntity player, ItemStack itemStack) { protected int getRequiredItemStackSlot(ClientPlayerEntity player) { if (player.getAbilities().creativeMode) { - return player.getInventory().selectedSlot; + return player.getInventory().getSelectedSlot(); } ItemStack requiredItem = getRequiredItem(player).stream().findFirst().orElse(ItemStack.EMPTY); diff --git a/v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/ButtonListenerMixin.java b/v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/ButtonListenerMixin.java index f4a673dcc..8d5be35eb 100644 --- a/v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/ButtonListenerMixin.java +++ b/v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/ButtonListenerMixin.java @@ -33,7 +33,7 @@ private void onActionPerformedWithButton(ButtonBase button, int mouseButton, Cal if (!warnType.get() || !PrinterConfig.AUTO_CONVERT_SCHEMATIC_TO_LITEMATIC_ON_LOAD.getBooleanValue()) { return; } - LitematicaSchematic newSchem = SchematicConverter.convertAndReturn(schematic.get().getFile(), entry.getDirectory()); + LitematicaSchematic newSchem = SchematicConverter.convertAndReturn(schematic.get().getFile().toFile(), entry.getDirectory()); warnType.set(false); schematic.set(newSchem); InfoUtils.showGuiOrInGameMessage(Message.MessageType.INFO, 15000, "Auto converted schematic to litematic format"); diff --git a/v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinMouse.java b/v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinMouse.java index 012115f76..efc8811b0 100644 --- a/v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinMouse.java +++ b/v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinMouse.java @@ -33,7 +33,7 @@ private void updateMouseChangeLookDirection(CallbackInfo ci) { double k = this.cursorDeltaX * h; double l = this.cursorDeltaY * h; int m = 1; - if (this.client.options.getInvertYMouse().getValue()) { + if (this.client.options.invertYMouse.getValue()) { m = -1; } float yaw = (float) (freeLook.getCameraYaw() + k * 0.15F); diff --git a/v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/PlayerInventoryAccessor.java b/v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/PlayerInventoryAccessor.java new file mode 100644 index 000000000..47a91b413 --- /dev/null +++ b/v1_21_4/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/PlayerInventoryAccessor.java @@ -0,0 +1,11 @@ +package me.aleksilassila.litematica.printer.v1_21_4.mixin; + +import net.minecraft.entity.player.PlayerInventory; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(PlayerInventory.class) +public interface PlayerInventoryAccessor { + @Accessor("selectedSlot") + void setSelectedSlot(int slot); +} diff --git a/v1_21_4/src/main/resources/litematica-printer.mixins.json b/v1_21_4/src/main/resources/litematica-printer.mixins.json index 8d1526eec..327b3b52b 100644 --- a/v1_21_4/src/main/resources/litematica-printer.mixins.json +++ b/v1_21_4/src/main/resources/litematica-printer.mixins.json @@ -15,6 +15,7 @@ "InputHandlerMixin", "MixinAccessorClientPlayerEntity", "MixinAccessorKeyBinding", + "PlayerInventoryAccessor", "MixinCamera", "MixinClientPlayerEntity", "MixinMouse", From 846f077c0f8265ddfb301aacb5ef347fef338c2c Mon Sep 17 00:00:00 2001 From: ejeebus Date: Sat, 6 Jun 2026 09:55:47 +0000 Subject: [PATCH 03/14] Fix v1_21_11 compilation errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apply all MC 1.21.4→1.21.11 API fixes to the v1_21_11 module: - PRESS/RELEASE_SHIFT_KEY → START/STOP_SNEAKING - PlayerInventory.main private field → getStack/setStack/36 - selectedSlot direct access → getSelectedSlot() / PlayerInventoryAccessor - InputUtil.isKeyPressed: long handle → Window - SchematicConverter.writeToFile: File → Path - ButtonListenerMixin.getFile() → getFile().toFile() - MixinMouse: getInvertYMouse() → invertYMouse - Entity.getWorld() → mc.world (ClientWorld) - Entity.getPos() → new Vec3d(getX(), getY(), getZ()) --- .../printer/v1_21_4/InventoryManager.java | 42 ++++++++++--------- .../printer/v1_21_4/MovementHandler.java | 4 +- .../litematica/printer/v1_21_4/Printer.java | 16 +++---- .../printer/v1_21_4/SchematicConverter.java | 2 +- .../printer/v1_21_4/actions/PresShift.java | 2 +- .../v1_21_4/actions/ReleaseShiftAction.java | 2 +- .../printer/v1_21_4/guides/Guide.java | 8 ++-- .../v1_21_4/mixin/ButtonListenerMixin.java | 2 +- .../printer/v1_21_4/mixin/MixinMouse.java | 2 +- .../mixin/PlayerInventoryAccessor.java | 11 +++++ .../resources/litematica-printer.mixins.json | 1 + 11 files changed, 53 insertions(+), 39 deletions(-) create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/PlayerInventoryAccessor.java diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/InventoryManager.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/InventoryManager.java index 4be0539c7..a9a227786 100644 --- a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/InventoryManager.java +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/InventoryManager.java @@ -108,7 +108,7 @@ private boolean swapToHotbar(ClientPlayerEntity player, Item item) { } hotbarSlots.get(nextSlot).addTicksLocked(10); hotbarSlots.get(nextSlot).waitingForItem = item; - player.getInventory().selectedSlot = nextSlot; + ((me.aleksilassila.litematica.printer.v1_21_4.mixin.PlayerInventoryAccessor) player.getInventory()).setSelectedSlot(nextSlot); if (PrinterConfig.PRINTER_DEBUG_LOG.getBooleanValue()) { System.out.println("Swapping item from inventory: " + slot + " into hotbar -> " + nextSlot); } @@ -127,7 +127,7 @@ public void pickSlot(WorldSchematic world, ClientPlayerEntity player, BlockPos p int slot = inv.getSlotWithStack(stack); boolean shouldPick = slot > 8; if (slot != -1 && !shouldPick) { - player.getInventory().selectedSlot = slot; + ((me.aleksilassila.litematica.printer.v1_21_4.mixin.PlayerInventoryAccessor) player.getInventory()).setSelectedSlot(slot); } else if (slot != -1) { InventoryUtils.setPickedItemToHand(slot, stack, mc); // https://github.com/sakura-ryoko/litematica-printer/blob/f8e38a2b31708e61f8a5fad0f2989d6834495da4/src/main/java/me/aleksilassila/litematica/printer/actions/PrepareAction.java#L71 } else if (Configs.Generic.PICK_BLOCK_SHULKERS.getBooleanValue()) { @@ -136,7 +136,7 @@ public void pickSlot(WorldSchematic world, ClientPlayerEntity player, BlockPos p if (slot > 8) { InventoryUtils.setPickedItemToHand(slot, stack, mc); } else { - inv.selectedSlot = slot; + ((me.aleksilassila.litematica.printer.v1_21_4.mixin.PlayerInventoryAccessor) inv).setSelectedSlot(slot); } } } @@ -154,7 +154,7 @@ public static int findSlotWithBoxWithItem(PlayerInventory inventory, ItemStack s int bestCount = lestFirst ? Integer.MAX_VALUE : 0; int bestSlot = -1; - for (int slotNum = 0; slotNum < inventory.main.size(); slotNum += 1) { + for (int slotNum = 0; slotNum < 36; slotNum += 1) { ItemStack itemStack = inventory.getStack(slotNum); int count = shulkerBoxItemCount(itemStack, stackReference); if (lestFirst && count < bestCount && count > 0) { @@ -197,8 +197,8 @@ public boolean select(ItemStack itemStack) { if (player.getAbilities().creativeMode) { depositCursorStack(); this.addPickBlock(inventory, itemStack); - mc.interactionManager.clickCreativeStack(player.getStackInHand(Hand.MAIN_HAND), 36 + inventory.selectedSlot); - updateLastUsedSlot(inventory.selectedSlot); + mc.interactionManager.clickCreativeStack(player.getStackInHand(Hand.MAIN_HAND), 36 + inventory.getSelectedSlot()); + updateLastUsedSlot(inventory.getSelectedSlot()); return true; } else { int hotbarSlot = getHotbarSlotWithItem(player, itemStack); @@ -214,9 +214,9 @@ public boolean select(ItemStack itemStack) { if (hotbarSlot == -1) { return false; } - if (hotbarSlot != player.getInventory().selectedSlot) { + if (hotbarSlot != player.getInventory().getSelectedSlot()) { player.networkHandler.sendPacket(new UpdateSelectedSlotC2SPacket(hotbarSlot)); - player.getInventory().selectedSlot = hotbarSlot; + ((me.aleksilassila.litematica.printer.v1_21_4.mixin.PlayerInventoryAccessor) player.getInventory()).setSelectedSlot(hotbarSlot); } updateLastUsedSlot(hotbarSlot); return false; @@ -225,9 +225,9 @@ public boolean select(ItemStack itemStack) { } else { depositCursorStack(); // Switch to hotbar slot - if (hotbarSlot != player.getInventory().selectedSlot) { + if (hotbarSlot != player.getInventory().getSelectedSlot()) { player.networkHandler.sendPacket(new UpdateSelectedSlotC2SPacket(hotbarSlot)); - player.getInventory().selectedSlot = hotbarSlot; + ((me.aleksilassila.litematica.printer.v1_21_4.mixin.PlayerInventoryAccessor) player.getInventory()).setSelectedSlot(hotbarSlot); } updateLastUsedSlot(hotbarSlot); return true; @@ -239,22 +239,24 @@ public boolean select(ItemStack itemStack) { // https://github.com/sakura-ryoko/litematica-printer/blob/f8e38a2b31708e61f8a5fad0f2989d6834495da4/src/main/java/me/aleksilassila/litematica/printer/actions/PrepareAction.java#L95 private void addPickBlock(PlayerInventory inv, ItemStack stack) { + me.aleksilassila.litematica.printer.v1_21_4.mixin.PlayerInventoryAccessor invAccessor = + (me.aleksilassila.litematica.printer.v1_21_4.mixin.PlayerInventoryAccessor) inv; int slot = inv.getSlotWithStack(stack); if (slot >= 0 && slot < 9) { - inv.selectedSlot = slot; + invAccessor.setSelectedSlot(slot); } else { if (slot == -1) { - inv.selectedSlot = inv.getSwappableHotbarSlot(); + invAccessor.setSelectedSlot(inv.getSwappableHotbarSlot()); - if (!inv.main.get(inv.selectedSlot).isEmpty()) { + if (!inv.getStack(inv.getSelectedSlot()).isEmpty()) { int empty = inv.getEmptySlot(); if (empty != -1) { - inv.main.set(empty, inv.main.get(inv.selectedSlot)); + inv.setStack(empty, inv.getStack(inv.getSelectedSlot())); } } - inv.main.set(inv.selectedSlot, stack); + inv.setStack(inv.getSelectedSlot(), stack); } else { inv.swapSlotWithHotbar(slot); } @@ -327,10 +329,10 @@ private static int getBestInventorySlotWithItem(ClientPlayerEntity player, ItemS int lowestCount = 0; int lowestSlot = -1; - for (int i = 9; i < inventory.main.size(); ++i) { - if (!(inventory.main.get(i)).isEmpty() && ItemStack.areItemsAndComponentsEqual(itemStack, inventory.main.get(i))) { - if (inventory.main.get(i).getCount() < lowestCount || lowestSlot == -1) { - lowestCount = inventory.main.get(i).getCount(); + for (int i = 9; i < 36; ++i) { + if (!inventory.getStack(i).isEmpty() && ItemStack.areItemsAndComponentsEqual(itemStack, inventory.getStack(i))) { + if (inventory.getStack(i).getCount() < lowestCount || lowestSlot == -1) { + lowestCount = inventory.getStack(i).getCount(); lowestSlot = i; } } @@ -348,7 +350,7 @@ public int getHotbarSlotWithItem(ClientPlayerEntity player, ItemStack itemStack) if (itemStack.isEmpty()) return -1; for (int i = 0; i < 9; ++i) { - if (!inventory.main.get(i).isEmpty() && ItemStack.areItemsEqual(inventory.main.get(i), itemStack)) { + if (!inventory.getStack(i).isEmpty() && ItemStack.areItemsEqual(inventory.getStack(i), itemStack)) { // if (hotbarSlots.get(i).ticksLocked == 0) { return i; // } diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/MovementHandler.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/MovementHandler.java index 9bc7c3fc1..e5866f8eb 100644 --- a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/MovementHandler.java +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/MovementHandler.java @@ -68,7 +68,7 @@ public void onGameTick() { } public static void grimRotate(ClientPlayerEntity player, float yaw, float pitch) { - Vec3d playerPos = player.getPos(); + Vec3d playerPos = new Vec3d(player.getX(), player.getY(), player.getZ()); mc.getNetworkHandler().sendPacket(new PlayerInputC2SPacket(player.input.playerInput)); mc.getNetworkHandler().sendPacket(new PlayerMoveC2SPacket.Full(playerPos.x, playerPos.y, playerPos.z, yaw, pitch, player.isOnGround(), player.horizontalCollision)); ((MixinAccessorClientPlayerEntity) mc.player).setLastYaw(yaw); @@ -207,6 +207,6 @@ static void apply(InputDirections direction) { } static boolean isKeyPressed(KeyBinding keyBinding) { - return InputUtil.isKeyPressed(MinecraftClient.getInstance().getWindow().getHandle(), ((MixinAccessorKeyBinding) keyBinding).getBoundKey().getCode()); + return InputUtil.isKeyPressed(MinecraftClient.getInstance().getWindow(), ((MixinAccessorKeyBinding) keyBinding).getBoundKey().getCode()); } } diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/Printer.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/Printer.java index b83faf023..993fcc8ac 100644 --- a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/Printer.java +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/Printer.java @@ -96,7 +96,7 @@ public boolean onGameTick() { findBlock: for (BlockPos position : positions) { - SchematicBlockState state = new SchematicBlockState(player.getWorld(), worldSchematic, position); + SchematicBlockState state = new SchematicBlockState(mc.world, worldSchematic, position); if (state.targetState.equals(state.currentState) || state.targetState.isAir()) continue; Guide[] guides = interactionGuides.getInteractionGuides(state); @@ -117,29 +117,29 @@ public boolean onGameTick() { private List getBlocksPlayerOccupied() { ArrayList positions = new ArrayList<>(); BlockPos playerPos = player.getBlockPos(); - int blocksHeightOccupied = (int) Math.ceil(player.getPos().y + player.getHeight() - playerPos.getY()); + int blocksHeightOccupied = (int) Math.ceil(player.getY() + player.getHeight() - playerPos.getY()); positions.add(player.getBlockPos()); positions.add(player.getBlockPos().up()); if (blocksHeightOccupied > 2) { positions.add(playerPos.up(2)); } - if (Math.floor(player.getPos().x + player.getWidth() / 2) > playerPos.getX()) { + if (Math.floor(player.getX() + player.getWidth() / 2) > playerPos.getX()) { for (int i = 0; i < blocksHeightOccupied; i++) { positions.add(playerPos.up(i).east()); } } - if ((player.getPos().x - player.getWidth() / 2) < playerPos.getX()) { + if ((player.getX() - player.getWidth() / 2) < playerPos.getX()) { for (int i = 0; i < blocksHeightOccupied; i++) { positions.add(playerPos.up(i).west()); } } - if (Math.floor(player.getPos().z + player.getWidth() / 2) > playerPos.getZ()) { + if (Math.floor(player.getZ() + player.getWidth() / 2) > playerPos.getZ()) { for (int i = 0; i < blocksHeightOccupied; i++) { positions.add(playerPos.up(i).south()); } } - if ((player.getPos().z - player.getWidth() / 2) < playerPos.getZ()) { + if ((player.getZ() - player.getWidth() / 2) < playerPos.getZ()) { for (int i = 0; i < blocksHeightOccupied; i++) { positions.add(playerPos.up(i).north()); } @@ -172,8 +172,8 @@ private List getReachablePositions() { return positions.stream() // .filter(p -> playerOccupied.stream().noneMatch(p::equals)) .sorted((a, b) -> { - double aDistance = this.player.getPos().squaredDistanceTo(Vec3d.ofCenter(a)); - double bDistance = this.player.getPos().squaredDistanceTo(Vec3d.ofCenter(b)); + double aDistance = new Vec3d(this.player.getX(), this.player.getY(), this.player.getZ()).squaredDistanceTo(Vec3d.ofCenter(a)); + double bDistance = new Vec3d(this.player.getX(), this.player.getY(), this.player.getZ()).squaredDistanceTo(Vec3d.ofCenter(b)); return Double.compare(aDistance, bDistance); }).toList(); } diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/SchematicConverter.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/SchematicConverter.java index c86c11dab..a99759a3c 100644 --- a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/SchematicConverter.java +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/SchematicConverter.java @@ -15,7 +15,7 @@ public static LitematicaSchematic convertAndReturn(File file, File out) { LitematicaSchematic schematic = LitematicaSchematicAccessor.invokeConstructor(file, FileType.VANILLA_STRUCTURE); schematic.readFromFile(); String fileName = file.getName().replace(".nbt", ""); - schematic.writeToFile(out, fileName, true); + schematic.writeToFile(out.toPath(), fileName, true); LitematicaSchematic newSchem = LitematicaSchematicAccessor.invokeConstructor(new File(out, fileName + ".litematic"), FileType.LITEMATICA_SCHEMATIC); newSchem.readFromFile(); return newSchem; diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/PresShift.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/PresShift.java index 84a7a456c..1cac5ed79 100644 --- a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/PresShift.java +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/PresShift.java @@ -11,7 +11,7 @@ public boolean send(MinecraftClient client, ClientPlayerEntity player) { player.input.playerInput = new PlayerInput(player.input.playerInput.forward(), player.input.playerInput.backward(), player.input.playerInput.left(), player.input.playerInput.right(), player.input.playerInput.jump(), true, player.input.playerInput.sprint()); - player.networkHandler.sendPacket(new ClientCommandC2SPacket(player, ClientCommandC2SPacket.Mode.PRESS_SHIFT_KEY)); + player.networkHandler.sendPacket(new ClientCommandC2SPacket(player, ClientCommandC2SPacket.Mode.START_SNEAKING)); return true; } } diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/ReleaseShiftAction.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/ReleaseShiftAction.java index 164ec3906..f4ae8b49b 100644 --- a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/ReleaseShiftAction.java +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/ReleaseShiftAction.java @@ -13,7 +13,7 @@ public boolean send(MinecraftClient client, ClientPlayerEntity player) { player.input.playerInput.backward(), player.input.playerInput.left(), player.input.playerInput.right(), player.input.playerInput.jump(), mc.options.sneakKey.isPressed(), player.input.playerInput.sprint()); if (!mc.options.sneakKey.isPressed()) { - player.networkHandler.sendPacket(new ClientCommandC2SPacket(player, ClientCommandC2SPacket.Mode.RELEASE_SHIFT_KEY)); + player.networkHandler.sendPacket(new ClientCommandC2SPacket(player, ClientCommandC2SPacket.Mode.STOP_SNEAKING)); } return true; } diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/Guide.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/Guide.java index 00d40aed3..3b4441c84 100644 --- a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/Guide.java +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/guides/Guide.java @@ -36,9 +36,9 @@ protected boolean playerHasRightItem(ClientPlayerEntity player) { public 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)) { + for (int i = 0; i < 36; ++i) { + if (itemStack.isEmpty() && inventory.getStack(i).isOf(itemStack.getItem())) return i; + if (!inventory.getStack(i).isEmpty() && ItemStack.areItemsEqual(inventory.getStack(i), itemStack)) { return i; } } @@ -48,7 +48,7 @@ public int getSlotWithItem(ClientPlayerEntity player, ItemStack itemStack) { protected int getRequiredItemStackSlot(ClientPlayerEntity player) { if (player.getAbilities().creativeMode) { - return player.getInventory().selectedSlot; + return player.getInventory().getSelectedSlot(); } ItemStack requiredItem = getRequiredItem(player).stream().findFirst().orElse(ItemStack.EMPTY); diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/ButtonListenerMixin.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/ButtonListenerMixin.java index f4a673dcc..8d5be35eb 100644 --- a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/ButtonListenerMixin.java +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/ButtonListenerMixin.java @@ -33,7 +33,7 @@ private void onActionPerformedWithButton(ButtonBase button, int mouseButton, Cal if (!warnType.get() || !PrinterConfig.AUTO_CONVERT_SCHEMATIC_TO_LITEMATIC_ON_LOAD.getBooleanValue()) { return; } - LitematicaSchematic newSchem = SchematicConverter.convertAndReturn(schematic.get().getFile(), entry.getDirectory()); + LitematicaSchematic newSchem = SchematicConverter.convertAndReturn(schematic.get().getFile().toFile(), entry.getDirectory()); warnType.set(false); schematic.set(newSchem); InfoUtils.showGuiOrInGameMessage(Message.MessageType.INFO, 15000, "Auto converted schematic to litematic format"); diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinMouse.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinMouse.java index 012115f76..efc8811b0 100644 --- a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinMouse.java +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinMouse.java @@ -33,7 +33,7 @@ private void updateMouseChangeLookDirection(CallbackInfo ci) { double k = this.cursorDeltaX * h; double l = this.cursorDeltaY * h; int m = 1; - if (this.client.options.getInvertYMouse().getValue()) { + if (this.client.options.invertYMouse.getValue()) { m = -1; } float yaw = (float) (freeLook.getCameraYaw() + k * 0.15F); diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/PlayerInventoryAccessor.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/PlayerInventoryAccessor.java new file mode 100644 index 000000000..47a91b413 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/PlayerInventoryAccessor.java @@ -0,0 +1,11 @@ +package me.aleksilassila.litematica.printer.v1_21_4.mixin; + +import net.minecraft.entity.player.PlayerInventory; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(PlayerInventory.class) +public interface PlayerInventoryAccessor { + @Accessor("selectedSlot") + void setSelectedSlot(int slot); +} diff --git a/v1_21_11/src/main/resources/litematica-printer.mixins.json b/v1_21_11/src/main/resources/litematica-printer.mixins.json index 8d1526eec..327b3b52b 100644 --- a/v1_21_11/src/main/resources/litematica-printer.mixins.json +++ b/v1_21_11/src/main/resources/litematica-printer.mixins.json @@ -15,6 +15,7 @@ "InputHandlerMixin", "MixinAccessorClientPlayerEntity", "MixinAccessorKeyBinding", + "PlayerInventoryAccessor", "MixinCamera", "MixinClientPlayerEntity", "MixinMouse", From c6e18018543e5b250325827fc9d7fbf89e9926f1 Mon Sep 17 00:00:00 2001 From: ejeebus Date: Sat, 6 Jun 2026 10:04:20 +0000 Subject: [PATCH 04/14] Fix remaining 1.21.11 API differences from 1.21.4 - PresShift/ReleaseShiftAction: replace ClientCommandC2SPacket sneaking modes (removed in 1.21.5+) with PlayerInputC2SPacket - ButtonListenerMixin: entry.getDirectory() returns Path, add .toFile() - MixinMouse: invertYMouse is now a method invertYMouse() in 1.21.11 --- .../litematica/printer/v1_21_4/actions/PresShift.java | 4 ++-- .../printer/v1_21_4/actions/ReleaseShiftAction.java | 6 ++---- .../printer/v1_21_4/mixin/ButtonListenerMixin.java | 2 +- .../litematica/printer/v1_21_4/mixin/MixinMouse.java | 2 +- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/PresShift.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/PresShift.java index 1cac5ed79..604cdbb53 100644 --- a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/PresShift.java +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/PresShift.java @@ -2,7 +2,7 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.ClientPlayerEntity; -import net.minecraft.network.packet.c2s.play.ClientCommandC2SPacket; +import net.minecraft.network.packet.c2s.play.PlayerInputC2SPacket; import net.minecraft.util.PlayerInput; public class PresShift extends Action { @@ -11,7 +11,7 @@ public boolean send(MinecraftClient client, ClientPlayerEntity player) { player.input.playerInput = new PlayerInput(player.input.playerInput.forward(), player.input.playerInput.backward(), player.input.playerInput.left(), player.input.playerInput.right(), player.input.playerInput.jump(), true, player.input.playerInput.sprint()); - player.networkHandler.sendPacket(new ClientCommandC2SPacket(player, ClientCommandC2SPacket.Mode.START_SNEAKING)); + player.networkHandler.sendPacket(new PlayerInputC2SPacket(player.input.playerInput)); return true; } } diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/ReleaseShiftAction.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/ReleaseShiftAction.java index f4ae8b49b..adad0c416 100644 --- a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/ReleaseShiftAction.java +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/actions/ReleaseShiftAction.java @@ -2,7 +2,7 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.ClientPlayerEntity; -import net.minecraft.network.packet.c2s.play.ClientCommandC2SPacket; +import net.minecraft.network.packet.c2s.play.PlayerInputC2SPacket; import net.minecraft.util.PlayerInput; public class ReleaseShiftAction extends Action { @@ -12,9 +12,7 @@ public boolean send(MinecraftClient client, ClientPlayerEntity player) { player.input.playerInput = new PlayerInput(player.input.playerInput.forward(), player.input.playerInput.backward(), player.input.playerInput.left(), player.input.playerInput.right(), player.input.playerInput.jump(), mc.options.sneakKey.isPressed(), player.input.playerInput.sprint()); - if (!mc.options.sneakKey.isPressed()) { - player.networkHandler.sendPacket(new ClientCommandC2SPacket(player, ClientCommandC2SPacket.Mode.STOP_SNEAKING)); - } + player.networkHandler.sendPacket(new PlayerInputC2SPacket(player.input.playerInput)); return true; } } diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/ButtonListenerMixin.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/ButtonListenerMixin.java index 8d5be35eb..07abd02e0 100644 --- a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/ButtonListenerMixin.java +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/ButtonListenerMixin.java @@ -33,7 +33,7 @@ private void onActionPerformedWithButton(ButtonBase button, int mouseButton, Cal if (!warnType.get() || !PrinterConfig.AUTO_CONVERT_SCHEMATIC_TO_LITEMATIC_ON_LOAD.getBooleanValue()) { return; } - LitematicaSchematic newSchem = SchematicConverter.convertAndReturn(schematic.get().getFile().toFile(), entry.getDirectory()); + LitematicaSchematic newSchem = SchematicConverter.convertAndReturn(schematic.get().getFile().toFile(), entry.getDirectory().toFile()); warnType.set(false); schematic.set(newSchem); InfoUtils.showGuiOrInGameMessage(Message.MessageType.INFO, 15000, "Auto converted schematic to litematic format"); diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinMouse.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinMouse.java index efc8811b0..2879d8940 100644 --- a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinMouse.java +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinMouse.java @@ -33,7 +33,7 @@ private void updateMouseChangeLookDirection(CallbackInfo ci) { double k = this.cursorDeltaX * h; double l = this.cursorDeltaY * h; int m = 1; - if (this.client.options.invertYMouse.getValue()) { + if (this.client.options.invertYMouse().getValue()) { m = -1; } float yaw = (float) (freeLook.getCameraYaw() + k * 0.15F); From c5d786e60e8867cf42f086a59f4403f391093b0b Mon Sep 17 00:00:00 2001 From: ejeebus Date: Sat, 6 Jun 2026 10:15:12 +0000 Subject: [PATCH 05/14] Fix invertYMouse private access in 1.21.11 via GameOptionsAccessor mixin --- .../printer/v1_21_4/mixin/GameOptionsAccessor.java | 12 ++++++++++++ .../litematica/printer/v1_21_4/mixin/MixinMouse.java | 2 +- .../main/resources/litematica-printer.mixins.json | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/GameOptionsAccessor.java diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/GameOptionsAccessor.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/GameOptionsAccessor.java new file mode 100644 index 000000000..8e81da759 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/GameOptionsAccessor.java @@ -0,0 +1,12 @@ +package me.aleksilassila.litematica.printer.v1_21_4.mixin; + +import net.minecraft.client.option.GameOptions; +import net.minecraft.client.option.SimpleOption; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(GameOptions.class) +public interface GameOptionsAccessor { + @Accessor("invertYMouse") + SimpleOption getInvertYMouse(); +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinMouse.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinMouse.java index 2879d8940..9408c0e90 100644 --- a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinMouse.java +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinMouse.java @@ -33,7 +33,7 @@ private void updateMouseChangeLookDirection(CallbackInfo ci) { double k = this.cursorDeltaX * h; double l = this.cursorDeltaY * h; int m = 1; - if (this.client.options.invertYMouse().getValue()) { + if (((GameOptionsAccessor) this.client.options).getInvertYMouse().getValue()) { m = -1; } float yaw = (float) (freeLook.getCameraYaw() + k * 0.15F); diff --git a/v1_21_11/src/main/resources/litematica-printer.mixins.json b/v1_21_11/src/main/resources/litematica-printer.mixins.json index 327b3b52b..130d45f3e 100644 --- a/v1_21_11/src/main/resources/litematica-printer.mixins.json +++ b/v1_21_11/src/main/resources/litematica-printer.mixins.json @@ -9,6 +9,7 @@ ], "client": [ "AxeItemAccessor", + "GameOptionsAccessor", "ButtonListenerMixin", "ConfigsMixin", "GuiConfigsMixin", From 3c561d1c144aa2d7c9c051d390a654823090c4ba Mon Sep 17 00:00:00 2001 From: ejeebus Date: Sat, 6 Jun 2026 10:20:12 +0000 Subject: [PATCH 06/14] =?UTF-8?q?Remove=20lastSneaking=20accessor=20?= =?UTF-8?q?=E2=80=94=20field=20removed=20from=20ClientPlayerEntity=20in=20?= =?UTF-8?q?1.21.11?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../v1_21_4/mixin/MixinAccessorClientPlayerEntity.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinAccessorClientPlayerEntity.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinAccessorClientPlayerEntity.java index e5da28b2e..a6890cc08 100644 --- a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinAccessorClientPlayerEntity.java +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinAccessorClientPlayerEntity.java @@ -9,12 +9,6 @@ public interface MixinAccessorClientPlayerEntity { @Accessor("ticksLeftToDoubleTapSprint") void setTicksLeftToDoubleTapSprint(int ticksLeftToDoubleTapSprint); - @Accessor("lastSneaking") - boolean getLastSneaking(); - - @Accessor("lastSneaking") - void setLastSneaking(boolean lastSneaking); - @Accessor("lastPitch") float getLastPitch(); @Accessor("lastPitch") From ee423f23b7542033177d473504fdde315a50c8e4 Mon Sep 17 00:00:00 2001 From: ejeebus Date: Sat, 6 Jun 2026 10:23:29 +0000 Subject: [PATCH 07/14] Fix lastPitch/lastYaw renamed to prevPitch/prevYaw and moved to Entity in 1.21.11 --- .../printer/v1_21_4/MovementHandler.java | 6 +++--- .../printer/v1_21_4/mixin/EntityAccessor.java | 20 +++++++++++++++++++ .../MixinAccessorClientPlayerEntity.java | 10 ---------- .../resources/litematica-printer.mixins.json | 1 + 4 files changed, 24 insertions(+), 13 deletions(-) create mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/EntityAccessor.java diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/MovementHandler.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/MovementHandler.java index e5866f8eb..9c8ecc670 100644 --- a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/MovementHandler.java +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/MovementHandler.java @@ -1,7 +1,7 @@ package me.aleksilassila.litematica.printer.v1_21_4; import me.aleksilassila.litematica.printer.v1_21_4.config.PrinterConfig; -import me.aleksilassila.litematica.printer.v1_21_4.mixin.MixinAccessorClientPlayerEntity; +import me.aleksilassila.litematica.printer.v1_21_4.mixin.EntityAccessor; import me.aleksilassila.litematica.printer.v1_21_4.mixin.MixinAccessorKeyBinding; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.ingame.CraftingScreen; @@ -71,8 +71,8 @@ public static void grimRotate(ClientPlayerEntity player, float yaw, float pitch) Vec3d playerPos = new Vec3d(player.getX(), player.getY(), player.getZ()); mc.getNetworkHandler().sendPacket(new PlayerInputC2SPacket(player.input.playerInput)); mc.getNetworkHandler().sendPacket(new PlayerMoveC2SPacket.Full(playerPos.x, playerPos.y, playerPos.z, yaw, pitch, player.isOnGround(), player.horizontalCollision)); - ((MixinAccessorClientPlayerEntity) mc.player).setLastYaw(yaw); - ((MixinAccessorClientPlayerEntity) mc.player).setLastPitch(pitch); + ((EntityAccessor) mc.player).setPrevYaw(yaw); + ((EntityAccessor) mc.player).setPrevPitch(pitch); } public void onDisable(ClientPlayerEntity player) { diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/EntityAccessor.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/EntityAccessor.java new file mode 100644 index 000000000..444fc0522 --- /dev/null +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/EntityAccessor.java @@ -0,0 +1,20 @@ +package me.aleksilassila.litematica.printer.v1_21_4.mixin; + +import net.minecraft.entity.Entity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(Entity.class) +public interface EntityAccessor { + @Accessor("prevYaw") + float getPrevYaw(); + + @Accessor("prevYaw") + void setPrevYaw(float prevYaw); + + @Accessor("prevPitch") + float getPrevPitch(); + + @Accessor("prevPitch") + void setPrevPitch(float prevPitch); +} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinAccessorClientPlayerEntity.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinAccessorClientPlayerEntity.java index a6890cc08..7a0a21e68 100644 --- a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinAccessorClientPlayerEntity.java +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinAccessorClientPlayerEntity.java @@ -8,14 +8,4 @@ public interface MixinAccessorClientPlayerEntity { @Accessor("ticksLeftToDoubleTapSprint") void setTicksLeftToDoubleTapSprint(int ticksLeftToDoubleTapSprint); - - @Accessor("lastPitch") - float getLastPitch(); - @Accessor("lastPitch") - void setLastPitch(float lastPitch); - - @Accessor("lastYaw") - float getLastYaw(); - @Accessor("lastYaw") - void setLastYaw(float lastYaw); } diff --git a/v1_21_11/src/main/resources/litematica-printer.mixins.json b/v1_21_11/src/main/resources/litematica-printer.mixins.json index 130d45f3e..f03e4f053 100644 --- a/v1_21_11/src/main/resources/litematica-printer.mixins.json +++ b/v1_21_11/src/main/resources/litematica-printer.mixins.json @@ -9,6 +9,7 @@ ], "client": [ "AxeItemAccessor", + "EntityAccessor", "GameOptionsAccessor", "ButtonListenerMixin", "ConfigsMixin", From deb5dfaf03a53e814df19448450679657ef8ed9b Mon Sep 17 00:00:00 2001 From: ejeebus Date: Sat, 6 Jun 2026 10:27:22 +0000 Subject: [PATCH 08/14] =?UTF-8?q?Remove=20EntityAccessor=20=E2=80=94=20pre?= =?UTF-8?q?vYaw/prevPitch=20don't=20exist=20in=20Entity=20in=201.21.11?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../printer/v1_21_4/MovementHandler.java | 3 --- .../printer/v1_21_4/mixin/EntityAccessor.java | 20 ------------------- .../resources/litematica-printer.mixins.json | 1 - 3 files changed, 24 deletions(-) delete mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/EntityAccessor.java diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/MovementHandler.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/MovementHandler.java index 9c8ecc670..08860e9b8 100644 --- a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/MovementHandler.java +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/MovementHandler.java @@ -1,7 +1,6 @@ package me.aleksilassila.litematica.printer.v1_21_4; import me.aleksilassila.litematica.printer.v1_21_4.config.PrinterConfig; -import me.aleksilassila.litematica.printer.v1_21_4.mixin.EntityAccessor; import me.aleksilassila.litematica.printer.v1_21_4.mixin.MixinAccessorKeyBinding; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.ingame.CraftingScreen; @@ -71,8 +70,6 @@ public static void grimRotate(ClientPlayerEntity player, float yaw, float pitch) Vec3d playerPos = new Vec3d(player.getX(), player.getY(), player.getZ()); mc.getNetworkHandler().sendPacket(new PlayerInputC2SPacket(player.input.playerInput)); mc.getNetworkHandler().sendPacket(new PlayerMoveC2SPacket.Full(playerPos.x, playerPos.y, playerPos.z, yaw, pitch, player.isOnGround(), player.horizontalCollision)); - ((EntityAccessor) mc.player).setPrevYaw(yaw); - ((EntityAccessor) mc.player).setPrevPitch(pitch); } public void onDisable(ClientPlayerEntity player) { diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/EntityAccessor.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/EntityAccessor.java deleted file mode 100644 index 444fc0522..000000000 --- a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/EntityAccessor.java +++ /dev/null @@ -1,20 +0,0 @@ -package me.aleksilassila.litematica.printer.v1_21_4.mixin; - -import net.minecraft.entity.Entity; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Accessor; - -@Mixin(Entity.class) -public interface EntityAccessor { - @Accessor("prevYaw") - float getPrevYaw(); - - @Accessor("prevYaw") - void setPrevYaw(float prevYaw); - - @Accessor("prevPitch") - float getPrevPitch(); - - @Accessor("prevPitch") - void setPrevPitch(float prevPitch); -} diff --git a/v1_21_11/src/main/resources/litematica-printer.mixins.json b/v1_21_11/src/main/resources/litematica-printer.mixins.json index f03e4f053..130d45f3e 100644 --- a/v1_21_11/src/main/resources/litematica-printer.mixins.json +++ b/v1_21_11/src/main/resources/litematica-printer.mixins.json @@ -9,7 +9,6 @@ ], "client": [ "AxeItemAccessor", - "EntityAccessor", "GameOptionsAccessor", "ButtonListenerMixin", "ConfigsMixin", From 14601b062605e53cbb769ebea5a4f02879366479 Mon Sep 17 00:00:00 2001 From: ejeebus Date: Sat, 6 Jun 2026 10:39:00 +0000 Subject: [PATCH 09/14] Fix invertYMouse crash: use getInvertMouseY() public method in 1.21.11 In MC 1.21.11 the field was renamed to invertMouseY and is now exposed via getInvertMouseY(). Remove GameOptionsAccessor mixin (which looked for the old invertYMouse field name and crashed at runtime) and call the public method directly from MixinMouse. --- .../printer/v1_21_4/mixin/GameOptionsAccessor.java | 12 ------------ .../litematica/printer/v1_21_4/mixin/MixinMouse.java | 2 +- .../main/resources/litematica-printer.mixins.json | 1 - 3 files changed, 1 insertion(+), 14 deletions(-) delete mode 100644 v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/GameOptionsAccessor.java diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/GameOptionsAccessor.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/GameOptionsAccessor.java deleted file mode 100644 index 8e81da759..000000000 --- a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/GameOptionsAccessor.java +++ /dev/null @@ -1,12 +0,0 @@ -package me.aleksilassila.litematica.printer.v1_21_4.mixin; - -import net.minecraft.client.option.GameOptions; -import net.minecraft.client.option.SimpleOption; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Accessor; - -@Mixin(GameOptions.class) -public interface GameOptionsAccessor { - @Accessor("invertYMouse") - SimpleOption getInvertYMouse(); -} diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinMouse.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinMouse.java index 9408c0e90..bc0a9f3e5 100644 --- a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinMouse.java +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinMouse.java @@ -33,7 +33,7 @@ private void updateMouseChangeLookDirection(CallbackInfo ci) { double k = this.cursorDeltaX * h; double l = this.cursorDeltaY * h; int m = 1; - if (((GameOptionsAccessor) this.client.options).getInvertYMouse().getValue()) { + if (this.client.options.getInvertMouseY().getValue()) { m = -1; } float yaw = (float) (freeLook.getCameraYaw() + k * 0.15F); diff --git a/v1_21_11/src/main/resources/litematica-printer.mixins.json b/v1_21_11/src/main/resources/litematica-printer.mixins.json index 130d45f3e..327b3b52b 100644 --- a/v1_21_11/src/main/resources/litematica-printer.mixins.json +++ b/v1_21_11/src/main/resources/litematica-printer.mixins.json @@ -9,7 +9,6 @@ ], "client": [ "AxeItemAccessor", - "GameOptionsAccessor", "ButtonListenerMixin", "ConfigsMixin", "GuiConfigsMixin", From 37a5f47beeb95be013b62e25baec432c6e9beb9c Mon Sep 17 00:00:00 2001 From: ejeebus Date: Sat, 6 Jun 2026 11:09:57 +0000 Subject: [PATCH 10/14] =?UTF-8?q?Remove=20PlayerMoveC2SPacketMixin=20from?= =?UTF-8?q?=201.21.11=20=E2=80=94=20ViaFabricPlus=20conflict?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The @ModifyVariable on PlayerMoveC2SPacket. conflicts with ViaFabricPlus's movement packet translation when connecting to 1.21.4 servers (like 2b2t), causing unknown packet id errors. The yaw/pitch override is handled separately by grimRotate so this mixin is not needed for core printer functionality. --- v1_21_11/src/main/resources/litematica-printer.mixins.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/v1_21_11/src/main/resources/litematica-printer.mixins.json b/v1_21_11/src/main/resources/litematica-printer.mixins.json index 327b3b52b..732f37aaa 100644 --- a/v1_21_11/src/main/resources/litematica-printer.mixins.json +++ b/v1_21_11/src/main/resources/litematica-printer.mixins.json @@ -18,8 +18,7 @@ "PlayerInventoryAccessor", "MixinCamera", "MixinClientPlayerEntity", - "MixinMouse", - "PlayerMoveC2SPacketMixin" + "MixinMouse" ], "injectors": { "defaultRequire": 1 From cc3912e20e0aab8f1930fd707e08b23f488c3a3d Mon Sep 17 00:00:00 2001 From: ejeebus Date: Mon, 8 Jun 2026 08:57:28 +0000 Subject: [PATCH 11/14] =?UTF-8?q?Remove=20MixinClientConnection=20from=201?= =?UTF-8?q?.21.11=20=E2=80=94=20packet=20pipeline=20conflict=20with=20ViaF?= =?UTF-8?q?abricPlus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- v1_21_11/src/main/resources/litematica-printer.mixins.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/v1_21_11/src/main/resources/litematica-printer.mixins.json b/v1_21_11/src/main/resources/litematica-printer.mixins.json index 732f37aaa..552844af6 100644 --- a/v1_21_11/src/main/resources/litematica-printer.mixins.json +++ b/v1_21_11/src/main/resources/litematica-printer.mixins.json @@ -4,8 +4,7 @@ "package": "me.aleksilassila.litematica.printer.v1_21_4.mixin", "compatibilityLevel": "JAVA_16", "mixins": [ - "LitematicaSchematicAccessor", - "MixinClientConnection" + "LitematicaSchematicAccessor" ], "client": [ "AxeItemAccessor", From a81361b350a1b23969a110f7ada1fa23f6a79b08 Mon Sep 17 00:00:00 2001 From: ejeebus Date: Tue, 9 Jun 2026 06:24:27 +0000 Subject: [PATCH 12/14] Fix LitematicaSchematicAccessor for sakura-ryoko litematica 1.21.11 The constructor signature changed from (File, FileType) to (Path, FileType) in the sakura-ryoko litematica fork for 1.21.11. The old @Invoker was causing a fatal mixin transformation failure on world join, which manifested as "Network Protocol Error" when connecting to 2b2t. --- .../litematica/printer/v1_21_4/SchematicConverter.java | 5 +++-- .../printer/v1_21_4/mixin/LitematicaSchematicAccessor.java | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/SchematicConverter.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/SchematicConverter.java index a99759a3c..0f5935dfb 100644 --- a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/SchematicConverter.java +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/SchematicConverter.java @@ -5,6 +5,7 @@ import me.aleksilassila.litematica.printer.v1_21_4.mixin.LitematicaSchematicAccessor; import java.io.File; +import java.nio.file.Path; /** * @author IceTank @@ -12,11 +13,11 @@ */ public class SchematicConverter { public static LitematicaSchematic convertAndReturn(File file, File out) { - LitematicaSchematic schematic = LitematicaSchematicAccessor.invokeConstructor(file, FileType.VANILLA_STRUCTURE); + LitematicaSchematic schematic = LitematicaSchematicAccessor.invokeConstructor(file.toPath(), FileType.VANILLA_STRUCTURE); schematic.readFromFile(); String fileName = file.getName().replace(".nbt", ""); schematic.writeToFile(out.toPath(), fileName, true); - LitematicaSchematic newSchem = LitematicaSchematicAccessor.invokeConstructor(new File(out, fileName + ".litematic"), FileType.LITEMATICA_SCHEMATIC); + LitematicaSchematic newSchem = LitematicaSchematicAccessor.invokeConstructor(out.toPath().resolve(fileName + ".litematic"), FileType.LITEMATICA_SCHEMATIC); newSchem.readFromFile(); return newSchem; } diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/LitematicaSchematicAccessor.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/LitematicaSchematicAccessor.java index abd189984..5e23cc0f2 100644 --- a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/LitematicaSchematicAccessor.java +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/LitematicaSchematicAccessor.java @@ -5,7 +5,7 @@ import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Invoker; -import java.io.File; +import java.nio.file.Path; /** * @author IceTank @@ -14,7 +14,7 @@ @Mixin(LitematicaSchematic.class) public interface LitematicaSchematicAccessor { @Invoker("") - static LitematicaSchematic invokeConstructor(File file, FileType fileType) { + static LitematicaSchematic invokeConstructor(Path file, FileType fileType) { throw new AssertionError(); } } From f5db9f740b4d8c10d536a215034e91abf38f8d92 Mon Sep 17 00:00:00 2001 From: ejeebus Date: Tue, 9 Jun 2026 06:27:42 +0000 Subject: [PATCH 13/14] Restore MixinClientConnection and PlayerMoveC2SPacketMixin to 1.21.11 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both were removed as guesswork to fix the Network Protocol Error on 2b2t. The actual root cause was LitematicaSchematicAccessor failing due to a constructor signature change (File→Path), now fixed separately. Neither mixin touched the packet pipeline in a way that would cause that error — restoring them to recover: - PlayerMoveC2SPacketMixin: yaw/pitch override for printer look actions - MixinClientConnection: airplace offhand slot suppression + 2b2t ghost item fix --- v1_21_11/src/main/resources/litematica-printer.mixins.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/v1_21_11/src/main/resources/litematica-printer.mixins.json b/v1_21_11/src/main/resources/litematica-printer.mixins.json index 552844af6..327b3b52b 100644 --- a/v1_21_11/src/main/resources/litematica-printer.mixins.json +++ b/v1_21_11/src/main/resources/litematica-printer.mixins.json @@ -4,7 +4,8 @@ "package": "me.aleksilassila.litematica.printer.v1_21_4.mixin", "compatibilityLevel": "JAVA_16", "mixins": [ - "LitematicaSchematicAccessor" + "LitematicaSchematicAccessor", + "MixinClientConnection" ], "client": [ "AxeItemAccessor", @@ -17,7 +18,8 @@ "PlayerInventoryAccessor", "MixinCamera", "MixinClientPlayerEntity", - "MixinMouse" + "MixinMouse", + "PlayerMoveC2SPacketMixin" ], "injectors": { "defaultRequire": 1 From 2fd6bdbdea6e9a80e4fb74001bc2f2d89538f8d9 Mon Sep 17 00:00:00 2001 From: ejeebus Date: Tue, 9 Jun 2026 11:20:32 +0000 Subject: [PATCH 14/14] Fix ghost blocks: only cancel redundant slot update packets The ghost item fix was cancelling all ScreenHandlerSlotUpdateS2CPacket for hotbar block items, including legitimate corrections when the server reduces item count after placement. This caused the client to believe it still had blocks it had already placed, producing ghost blocks. Now only cancel if the incoming packet is a true duplicate (same item type, components, and count). Actual count corrections from the server are allowed through so the client inventory stays in sync. --- .../printer/v1_21_4/mixin/MixinClientConnection.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinClientConnection.java b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinClientConnection.java index 6622642f1..fe9f02e53 100644 --- a/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinClientConnection.java +++ b/v1_21_11/src/main/java/me/aleksilassila/litematica/printer/v1_21_4/mixin/MixinClientConnection.java @@ -50,8 +50,15 @@ private void channelReadPre(ChannelHandlerContext channelHandlerContext, Packet< // Looks like faulty packet is always on syncId = 0 if (packet1.getSyncId() == 0 && mc.player != null) { if (packet1.getSlot() >= PlayerScreenHandler.HOTBAR_START || packet1.getSlot() < PlayerScreenHandler.HOTBAR_END) { - // Only cancel updates to block items. Some blocks might not be in the USABLE_SLOTS list but still get used for placing - if (mc.player.playerScreenHandler.getSlot(packet1.getSlot()).getStack().getItem() instanceof BlockItem) { + net.minecraft.item.ItemStack current = mc.player.playerScreenHandler.getSlot(packet1.getSlot()).getStack(); + net.minecraft.item.ItemStack incoming = packet1.getStack(); + // Only cancel if it's a redundant duplicate (same item AND same count). + // Allow through if the server is correcting the count — blocking those + // causes the client to think it still has blocks it already placed, + // which results in ghost block placements. + if (current.getItem() instanceof BlockItem + && net.minecraft.item.ItemStack.areItemsAndComponentsEqual(current, incoming) + && current.getCount() == incoming.getCount()) { callback.cancel(); } }