├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src └── main ├── java └── net │ └── earthcomputer │ └── litemoretica │ ├── client │ ├── EasyPlaceFix.java │ ├── EasyPlaceProtocolClient.java │ ├── LitemoreticaClient.java │ ├── LitemoreticaConfigs.java │ ├── LitemoreticaHotkeys.java │ ├── LitemoreticaSchematicUtils.java │ └── PasteHandlerClient.java │ ├── mixin │ ├── LitemoreticaMixinPlugin.java │ ├── client │ │ ├── ClientPlayNetworkHandlerMixin_PacketSplitter.java │ │ ├── ClientPlayerInteractionManagerMixin_EasyPlaceFix.java │ │ ├── ConfigsGenericMixin.java │ │ ├── HotkeysMixin.java │ │ ├── PlacementHandlerAccessor.java │ │ ├── PlacementHandlerMixin_EasyPlaceProtocol.java │ │ ├── RayTraceUtilsMixin_ShapeContextFix.java │ │ ├── SchematicPlacementManagerMixin_PasteHandler.java │ │ ├── SchematicUtilsAccessor.java │ │ ├── TaskCountBlocksPlacementMixin_MaterialListFeature.java │ │ ├── WorldUtilsAccessor.java │ │ └── WorldUtilsMixin_EasyPlaceFix.java │ └── server │ │ ├── BlockItemMixin_EasyPlaceProtocol.java │ │ ├── ServerPlayNetworkHandlerMixin_EasyPlaceProtocol.java │ │ └── ServerPlayNetworkHandlerMixin_PacketSplitter.java │ ├── network │ ├── InitEasyPlaceProtocolPacket.java │ ├── PacketSplitter.java │ ├── SetEasyPlaceProtocolPacket.java │ └── UploadChunkPacket.java │ └── server │ ├── CarpetCompat.java │ ├── EasyPlaceProtocolServer.java │ ├── LitemoreticaServer.java │ └── PasteHandlerServer.java └── resources ├── assets └── litemoretica │ └── icon.png ├── fabric.mod.json ├── litemoretica.client.mixins.json └── litemoretica.server.mixins.json /.gitignore: -------------------------------------------------------------------------------- 1 | # User-specific stuff 2 | .idea/ 3 | 4 | *.iml 5 | *.ipr 6 | *.iws 7 | 8 | # IntelliJ 9 | out/ 10 | # mpeltonen/sbt-idea plugin 11 | .idea_modules/ 12 | 13 | # JIRA plugin 14 | atlassian-ide-plugin.xml 15 | 16 | # Compiled class file 17 | *.class 18 | 19 | # Log file 20 | *.log 21 | 22 | # BlueJ files 23 | *.ctxt 24 | 25 | # Package Files # 26 | *.jar 27 | *.war 28 | *.nar 29 | *.ear 30 | *.zip 31 | *.tar.gz 32 | *.rar 33 | 34 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 35 | hs_err_pid* 36 | 37 | *~ 38 | 39 | # temporary files which can be created if a process still has a handle open of a deleted file 40 | .fuse_hidden* 41 | 42 | # KDE directory preferences 43 | .directory 44 | 45 | # Linux trash folder which might appear on any partition or disk 46 | .Trash-* 47 | 48 | # .nfs files are created when an open file is removed but is still being accessed 49 | .nfs* 50 | 51 | # General 52 | .DS_Store 53 | .AppleDouble 54 | .LSOverride 55 | 56 | # Icon must end with two \r 57 | Icon 58 | 59 | # Thumbnails 60 | ._* 61 | 62 | # Files that might appear in the root of a volume 63 | .DocumentRevisions-V100 64 | .fseventsd 65 | .Spotlight-V100 66 | .TemporaryItems 67 | .Trashes 68 | .VolumeIcon.icns 69 | .com.apple.timemachine.donotpresent 70 | 71 | # Directories potentially created on remote AFP share 72 | .AppleDB 73 | .AppleDesktop 74 | Network Trash Folder 75 | Temporary Items 76 | .apdisk 77 | 78 | # Windows thumbnail cache files 79 | Thumbs.db 80 | Thumbs.db:encryptable 81 | ehthumbs.db 82 | ehthumbs_vista.db 83 | 84 | # Dump file 85 | *.stackdump 86 | 87 | # Folder config file 88 | [Dd]esktop.ini 89 | 90 | # Recycle Bin used on file shares 91 | $RECYCLE.BIN/ 92 | 93 | # Windows Installer files 94 | *.cab 95 | *.msi 96 | *.msix 97 | *.msm 98 | *.msp 99 | 100 | # Windows shortcuts 101 | *.lnk 102 | 103 | .gradle 104 | build/ 105 | 106 | # Ignore Gradle GUI config 107 | gradle-app.setting 108 | 109 | # Cache of project 110 | .gradletasknamecache 111 | 112 | **/build/ 113 | 114 | # Common working directory 115 | run/ 116 | 117 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 118 | !gradle-wrapper.jar 119 | 120 | changelog.txt 121 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023 Earthcomputer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Litemoretica 2 | 3 | Adds features and fixes to the [Litematica mod](https://www.curseforge.com/minecraft/mc-mods/litematica). 4 | This mod should be installed alongisde Litematica. 5 | 6 | - `schematicEditReplaceSelection`: Copies the selected area(s) from the world into the litematic. This is useful for telling Litematica that an area is correct. 7 | - Easy place fixes: 8 | - Prevent easy place from placing blocks where it shouldn't. 9 | - Fixes an [incompatibility](https://github.com/AmyMialeeMods/visible-barriers/issues/22) with [Visible Barriers](https://modrinth.com/mod/visiblebarriers). 10 | 11 | You can install Litemoretica on the server for additional features. This does *not* require Litematica on the server, but *does* require it on the client. 12 | - Easy place protocol version 3. Correct placement of more blocks than version 2, such as hoppers and buttons. 13 | - Fast and accurate pasting of schematics in multiplayer 14 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'fabric-loom' version '1.5-SNAPSHOT' 3 | id 'com.modrinth.minotaur' version '2.+' 4 | id 'maven-publish' 5 | } 6 | 7 | version = project.mod_version 8 | group = project.maven_group 9 | 10 | modrinth { 11 | token = project.hasProperty('modrinthKey') ? modrinthKey : null 12 | projectId = 'litemoretica' 13 | uploadFile = remapJar 14 | version = "${project.version}+mc${project.minecraft_version}" 15 | gameVersions.set(project.modrinth_compatible_versions.split(',').toList()) 16 | loaders.set(['fabric', 'quilt']) 17 | def changelogFile = file('changelog.txt') 18 | changelog = changelogFile.exists() ? changelogFile.text : '' 19 | } 20 | 21 | repositories { 22 | maven { 23 | name = 'Modrinth' 24 | url = 'https://api.modrinth.com/maven' 25 | content { 26 | includeGroup 'maven.modrinth' 27 | } 28 | } 29 | } 30 | 31 | dependencies { 32 | // To change the versions see the gradle.properties file 33 | minecraft "com.mojang:minecraft:${project.minecraft_version}" 34 | mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" 35 | modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" 36 | 37 | modImplementation fabricApi.module('fabric-resource-loader-v0', project.fabric_api_version) 38 | modImplementation fabricApi.module('fabric-networking-api-v1', project.fabric_api_version) 39 | modImplementation "maven.modrinth:malilib:${project.malilib_version}" 40 | modImplementation "maven.modrinth:litematica:${project.litematica_version}" 41 | modImplementation "maven.modrinth:carpet:${project.carpet_version}" 42 | } 43 | 44 | processResources { 45 | inputs.property "version", project.version 46 | inputs.property "minecraft_version", project.minecraft_version 47 | inputs.property "loader_version", project.loader_version 48 | filteringCharset "UTF-8" 49 | 50 | filesMatching("fabric.mod.json") { 51 | expand "version": project.version, 52 | "mcversions": project.modjson_compatible_versions 53 | } 54 | } 55 | 56 | def targetJavaVersion = 17 57 | tasks.withType(JavaCompile).configureEach { 58 | // ensure that the encoding is set to UTF-8, no matter what the system default is 59 | // this fixes some edge cases with special characters not displaying correctly 60 | // see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html 61 | // If Javadoc is generated, this must be specified in that task too. 62 | it.options.encoding = "UTF-8" 63 | if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) { 64 | it.options.release = targetJavaVersion 65 | } 66 | } 67 | 68 | java { 69 | def javaVersion = JavaVersion.toVersion(targetJavaVersion) 70 | if (JavaVersion.current() < javaVersion) { 71 | toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion) 72 | } 73 | archivesBaseName = project.archives_base_name 74 | // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task 75 | // if it is present. 76 | // If you remove this line, sources will not be generated. 77 | withSourcesJar() 78 | } 79 | 80 | jar { 81 | from("LICENSE") { 82 | rename { "${it}_${project.archivesBaseName}" } 83 | } 84 | } 85 | 86 | // configure the maven publication 87 | publishing { 88 | publications { 89 | mavenJava(MavenPublication) { 90 | from components.java 91 | } 92 | } 93 | 94 | // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. 95 | repositories { 96 | // Add repositories to publish to here. 97 | // Notice: This block does NOT have the same function as the block in the top level. 98 | // The repositories here will be used for publishing your artifact, not for 99 | // retrieving dependencies. 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Done to increase the memory available to gradle. 2 | org.gradle.jvmargs=-Xmx1G 3 | 4 | # Fabric Properties 5 | # check these on https://modmuss50.me/fabric.html 6 | minecraft_version=1.20.2 7 | yarn_mappings=1.20.2+build.4 8 | loader_version=0.15.7 9 | 10 | # Mod Properties 11 | mod_version = 1.2.5 12 | maven_group = net.earthcomputer 13 | archives_base_name = litemoretica 14 | 15 | modrinth_compatible_versions = 1.20.2 16 | modjson_compatible_versions = 1.20.2 17 | 18 | # Dependencies 19 | fabric_api_version=0.91.6+1.20.2 20 | malilib_version=0.17.0 21 | litematica_version=0.16.1 22 | carpet_version=1.4.121 23 | 24 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Earthcomputer/litemoretica/817a384da157aeb7ff0a051a4d8b8711f04f5ee3/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Use the maximum available, or set MAX_FD != -1 to use that value. 89 | MAX_FD=maximum 90 | 91 | warn () { 92 | echo "$*" 93 | } >&2 94 | 95 | die () { 96 | echo 97 | echo "$*" 98 | echo 99 | exit 1 100 | } >&2 101 | 102 | # OS specific support (must be 'true' or 'false'). 103 | cygwin=false 104 | msys=false 105 | darwin=false 106 | nonstop=false 107 | case "$( uname )" in #( 108 | CYGWIN* ) cygwin=true ;; #( 109 | Darwin* ) darwin=true ;; #( 110 | MSYS* | MINGW* ) msys=true ;; #( 111 | NONSTOP* ) nonstop=true ;; 112 | esac 113 | 114 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 115 | 116 | 117 | # Determine the Java command to use to start the JVM. 118 | if [ -n "$JAVA_HOME" ] ; then 119 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 120 | # IBM's JDK on AIX uses strange locations for the executables 121 | JAVACMD=$JAVA_HOME/jre/sh/java 122 | else 123 | JAVACMD=$JAVA_HOME/bin/java 124 | fi 125 | if [ ! -x "$JAVACMD" ] ; then 126 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 127 | 128 | Please set the JAVA_HOME variable in your environment to match the 129 | location of your Java installation." 130 | fi 131 | else 132 | JAVACMD=java 133 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 134 | 135 | Please set the JAVA_HOME variable in your environment to match the 136 | location of your Java installation." 137 | fi 138 | 139 | # Increase the maximum file descriptors if we can. 140 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 141 | case $MAX_FD in #( 142 | max*) 143 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 144 | # shellcheck disable=SC3045 145 | MAX_FD=$( ulimit -H -n ) || 146 | warn "Could not query maximum file descriptor limit" 147 | esac 148 | case $MAX_FD in #( 149 | '' | soft) :;; #( 150 | *) 151 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 152 | # shellcheck disable=SC3045 153 | ulimit -n "$MAX_FD" || 154 | warn "Could not set maximum file descriptor limit to $MAX_FD" 155 | esac 156 | fi 157 | 158 | # Collect all arguments for the java command, stacking in reverse order: 159 | # * args from the command line 160 | # * the main class name 161 | # * -classpath 162 | # * -D...appname settings 163 | # * --module-path (only if needed) 164 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 165 | 166 | # For Cygwin or MSYS, switch paths to Windows format before running java 167 | if "$cygwin" || "$msys" ; then 168 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 169 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 170 | 171 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 172 | 173 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 174 | for arg do 175 | if 176 | case $arg in #( 177 | -*) false ;; # don't mess with options #( 178 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 179 | [ -e "$t" ] ;; #( 180 | *) false ;; 181 | esac 182 | then 183 | arg=$( cygpath --path --ignore --mixed "$arg" ) 184 | fi 185 | # Roll the args list around exactly as many times as the number of 186 | # args, so each arg winds up back in the position where it started, but 187 | # possibly modified. 188 | # 189 | # NB: a `for` loop captures its iteration list before it begins, so 190 | # changing the positional parameters here affects neither the number of 191 | # iterations, nor the values presented in `arg`. 192 | shift # remove old arg 193 | set -- "$@" "$arg" # push replacement arg 194 | done 195 | fi 196 | 197 | 198 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 199 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 200 | 201 | # Collect all arguments for the java command; 202 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 203 | # shell script including quotes and variable substitutions, so put them in 204 | # double quotes to make sure that they get re-expanded; and 205 | # * put everything else in single quotes, so that it's not re-expanded. 206 | 207 | set -- \ 208 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 209 | -classpath "$CLASSPATH" \ 210 | org.gradle.wrapper.GradleWrapperMain \ 211 | "$@" 212 | 213 | # Stop when "xargs" is not available. 214 | if ! command -v xargs >/dev/null 2>&1 215 | then 216 | die "xargs is not available" 217 | fi 218 | 219 | # Use "xargs" to parse quoted args. 220 | # 221 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 222 | # 223 | # In Bash we could simply go: 224 | # 225 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 226 | # set -- "${ARGS[@]}" "$@" 227 | # 228 | # but POSIX shell has neither arrays nor command substitution, so instead we 229 | # post-process each arg (as a line of input to sed) to backslash-escape any 230 | # character that might be a shell metacharacter, then use eval to reverse 231 | # that process (while maintaining the separation between arguments), and wrap 232 | # the whole thing up as a single "set" statement. 233 | # 234 | # This will of course break if any of these variables contains a newline or 235 | # an unmatched quote. 236 | # 237 | 238 | eval "set -- $( 239 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 240 | xargs -n1 | 241 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 242 | tr '\n' ' ' 243 | )" '"$@"' 244 | 245 | exec "$JAVACMD" "$@" 246 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | maven { 4 | name = 'Fabric' 5 | url = 'https://maven.fabricmc.net/' 6 | } 7 | gradlePluginPortal() 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/litemoretica/client/EasyPlaceFix.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.litemoretica.client; 2 | 3 | import fi.dy.masa.litematica.config.Configs; 4 | import fi.dy.masa.malilib.gui.Message; 5 | import fi.dy.masa.malilib.util.InfoUtils; 6 | import fi.dy.masa.malilib.util.MessageOutputType; 7 | import net.earthcomputer.litemoretica.mixin.client.WorldUtilsAccessor; 8 | import net.minecraft.client.MinecraftClient; 9 | 10 | public final class EasyPlaceFix { 11 | public static boolean isPlacingWithEasyPlace = false; 12 | 13 | private EasyPlaceFix() { 14 | } 15 | 16 | public static boolean handleEasyPlaceRestriction(MinecraftClient mc) { 17 | boolean cancel = WorldUtilsAccessor.invokePlacementRestrictionInEffect(mc); 18 | 19 | if (cancel) { 20 | MessageOutputType type = (MessageOutputType) Configs.Generic.PLACEMENT_RESTRICTION_WARN.getOptionListValue(); 21 | 22 | if (type == MessageOutputType.MESSAGE) { 23 | InfoUtils.showGuiOrInGameMessage(Message.MessageType.WARNING, "litematica.message.easy_place_fail"); 24 | } else if (type == MessageOutputType.ACTIONBAR) { 25 | InfoUtils.printActionbarMessage("litematica.message.easy_place_fail"); 26 | } 27 | } 28 | 29 | return cancel; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/litemoretica/client/EasyPlaceProtocolClient.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.litemoretica.client; 2 | 3 | import com.google.common.collect.ImmutableSet; 4 | import fi.dy.masa.litematica.config.Configs; 5 | import fi.dy.masa.litematica.util.PlacementHandler; 6 | import io.netty.buffer.Unpooled; 7 | import net.earthcomputer.litemoretica.mixin.client.PlacementHandlerAccessor; 8 | import net.earthcomputer.litemoretica.network.InitEasyPlaceProtocolPacket; 9 | import net.earthcomputer.litemoretica.network.SetEasyPlaceProtocolPacket; 10 | import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; 11 | import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; 12 | import net.minecraft.client.MinecraftClient; 13 | import net.minecraft.network.PacketByteBuf; 14 | import net.minecraft.state.property.Property; 15 | 16 | public final class EasyPlaceProtocolClient { 17 | private static final ImmutableSet> DEFAULT_WHITELISTED_PROPERTIES = PlacementHandler.WHITELISTED_PROPERTIES; 18 | public static boolean serverHasV3Protocol = false; 19 | 20 | private EasyPlaceProtocolClient() { 21 | } 22 | 23 | public static void init() { 24 | ClientPlayNetworking.registerGlobalReceiver(InitEasyPlaceProtocolPacket.TYPE, (client, handler, buf, responseSender) -> { 25 | InitEasyPlaceProtocolPacket packet = new InitEasyPlaceProtocolPacket(buf); 26 | MinecraftClient.getInstance().execute(() -> { 27 | PlacementHandlerAccessor.setWhitelistedProperties(packet.whitelistedProperties()); 28 | }); 29 | }); 30 | ClientPlayConnectionEvents.JOIN.register((handler, sender, client) -> { 31 | if (ClientPlayNetworking.canSend(SetEasyPlaceProtocolPacket.TYPE)) { 32 | PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer()); 33 | new SetEasyPlaceProtocolPacket(getEasyPlaceProtocol()).write(buf); 34 | sender.sendPacket(SetEasyPlaceProtocolPacket.TYPE, buf); 35 | serverHasV3Protocol = true; 36 | } 37 | }); 38 | ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> { 39 | PlacementHandlerAccessor.setWhitelistedProperties(DEFAULT_WHITELISTED_PROPERTIES); 40 | serverHasV3Protocol = false; 41 | }); 42 | 43 | Configs.Generic.EASY_PLACE_PROTOCOL.setValueChangeCallback(config -> { 44 | if (ClientPlayNetworking.canSend(SetEasyPlaceProtocolPacket.TYPE)) { 45 | PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer()); 46 | new SetEasyPlaceProtocolPacket(getEasyPlaceProtocol()).write(buf); 47 | ClientPlayNetworking.send(SetEasyPlaceProtocolPacket.TYPE, buf); 48 | } 49 | }); 50 | } 51 | 52 | private static int getEasyPlaceProtocol() { 53 | return switch (PlacementHandler.getEffectiveProtocolVersion()) { 54 | case V3 -> 3; 55 | case V2 -> 2; 56 | case SLAB_ONLY -> 1; 57 | default -> 0; 58 | }; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/litemoretica/client/LitemoreticaClient.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.litemoretica.client; 2 | 3 | import fi.dy.masa.malilib.event.InitializationHandler; 4 | import net.fabricmc.api.ClientModInitializer; 5 | import net.fabricmc.loader.api.FabricLoader; 6 | import net.minecraft.client.MinecraftClient; 7 | 8 | public class LitemoreticaClient implements ClientModInitializer { 9 | public static final boolean HAS_NETWORKING = FabricLoader.getInstance().isModLoaded("fabric-networking-api-v1"); 10 | 11 | @Override 12 | public void onInitializeClient() { 13 | InitializationHandler.getInstance().registerInitializationHandler(() -> { 14 | LitemoreticaHotkeys.addCallbacks(MinecraftClient.getInstance()); 15 | }); 16 | 17 | if (HAS_NETWORKING) { 18 | EasyPlaceProtocolClient.init(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/litemoretica/client/LitemoreticaConfigs.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.litemoretica.client; 2 | 3 | import fi.dy.masa.malilib.config.IConfigBase; 4 | import fi.dy.masa.malilib.config.options.ConfigBoolean; 5 | 6 | public final class LitemoreticaConfigs { 7 | private LitemoreticaConfigs() { 8 | } 9 | 10 | public static final ConfigBoolean MATERIAL_LIST_IGNORE_BLOCK_STATE = new ConfigBoolean("materialListIgnoreBlockState", true, "If true, ignores wrong blockstates\nwhen generating material lists"); 11 | 12 | public static IConfigBase[] getExtraGenericConfigs() { 13 | return new IConfigBase[] { 14 | MATERIAL_LIST_IGNORE_BLOCK_STATE, 15 | }; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/litemoretica/client/LitemoreticaHotkeys.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.litemoretica.client; 2 | 3 | import fi.dy.masa.malilib.config.options.ConfigHotkey; 4 | import fi.dy.masa.malilib.hotkeys.IHotkeyCallback; 5 | import fi.dy.masa.malilib.hotkeys.IKeybind; 6 | import fi.dy.masa.malilib.hotkeys.KeyAction; 7 | import net.minecraft.client.MinecraftClient; 8 | 9 | public final class LitemoreticaHotkeys { 10 | public static final ConfigHotkey SCHEMATIC_EDIT_REPLACE_SELECTION = new ConfigHotkey("schematicEditReplaceSelection", "", "A hotkey to copy the blocks in the world within the area selection into the litematic"); 11 | 12 | private LitemoreticaHotkeys() { 13 | } 14 | 15 | public static ConfigHotkey[] getExtraHotkeys() { 16 | return new ConfigHotkey[] { 17 | SCHEMATIC_EDIT_REPLACE_SELECTION, 18 | }; 19 | } 20 | 21 | public static void addCallbacks(MinecraftClient mc) { 22 | Callback callback = new Callback(mc); 23 | 24 | SCHEMATIC_EDIT_REPLACE_SELECTION.getKeybind().setCallback(callback); 25 | } 26 | 27 | private static final class Callback implements IHotkeyCallback { 28 | private final MinecraftClient mc; 29 | 30 | private Callback(MinecraftClient mc) { 31 | this.mc = mc; 32 | } 33 | 34 | @Override 35 | public boolean onKeyAction(KeyAction action, IKeybind key) { 36 | if (mc.player == null || mc.world == null) { 37 | return false; 38 | } 39 | 40 | if (key == SCHEMATIC_EDIT_REPLACE_SELECTION.getKeybind()) { 41 | return LitemoreticaSchematicUtils.saveAreaSelectionToSchematic(mc.world); 42 | } 43 | 44 | return false; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/litemoretica/client/LitemoreticaSchematicUtils.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.litemoretica.client; 2 | 3 | import fi.dy.masa.litematica.data.DataManager; 4 | import fi.dy.masa.litematica.selection.AreaSelection; 5 | import fi.dy.masa.litematica.selection.Box; 6 | import fi.dy.masa.litematica.selection.SelectionManager; 7 | import fi.dy.masa.litematica.world.SchematicWorldHandler; 8 | import fi.dy.masa.litematica.world.WorldSchematic; 9 | import net.earthcomputer.litemoretica.mixin.client.SchematicUtilsAccessor; 10 | import net.minecraft.block.BlockState; 11 | import net.minecraft.util.math.BlockPos; 12 | import net.minecraft.world.World; 13 | 14 | public final class LitemoreticaSchematicUtils { 15 | private LitemoreticaSchematicUtils() { 16 | } 17 | 18 | public static boolean saveAreaSelectionToSchematic(World mcWorld) { 19 | SelectionManager selectionManager = DataManager.getSelectionManager(); 20 | AreaSelection currentSelection = selectionManager.getCurrentSelection(); 21 | if (currentSelection == null) { 22 | return false; 23 | } 24 | 25 | WorldSchematic schematicWorld = SchematicWorldHandler.getSchematicWorld(); 26 | if (schematicWorld == null) { 27 | return false; 28 | } 29 | 30 | for (Box subregion : currentSelection.getAllSubRegionBoxes()) { 31 | BlockPos pos1 = subregion.getPos1(); 32 | BlockPos pos2 = subregion.getPos2(); 33 | if (pos1 == null || pos2 == null) { 34 | continue; 35 | } 36 | 37 | BlockPos.Mutable pos = new BlockPos.Mutable(); 38 | for (int y = Math.min(pos1.getY(), pos2.getY()), yEnd = Math.max(pos1.getY(), pos2.getY()); y <= yEnd; y++) { 39 | for (int x = Math.min(pos1.getX(), pos2.getX()), xEnd = Math.max(pos1.getX(), pos2.getX()); x <= xEnd; x++) { 40 | for (int z = Math.min(pos1.getZ(), pos2.getZ()), zEnd = Math.max(pos1.getZ(), pos2.getZ()); z <= zEnd; z++) { 41 | pos.set(x, y, z); 42 | BlockState worldState = mcWorld.getBlockState(pos); 43 | BlockState schematicState = schematicWorld.getBlockState(pos); 44 | if (worldState != schematicState) { 45 | SchematicUtilsAccessor.invokeSetTargetedSchematicBlockState(pos, worldState); 46 | } 47 | } 48 | } 49 | } 50 | } 51 | 52 | return true; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/litemoretica/client/PasteHandlerClient.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.litemoretica.client; 2 | 3 | import com.google.common.collect.ArrayListMultimap; 4 | import fi.dy.masa.litematica.config.Configs; 5 | import fi.dy.masa.litematica.render.infohud.InfoHud; 6 | import fi.dy.masa.litematica.scheduler.tasks.TaskPasteSchematicPerChunkBase; 7 | import fi.dy.masa.litematica.schematic.LitematicaSchematic; 8 | import fi.dy.masa.litematica.schematic.container.LitematicaBlockStateContainer; 9 | import fi.dy.masa.litematica.schematic.placement.SchematicPlacement; 10 | import fi.dy.masa.litematica.schematic.placement.SubRegionPlacement; 11 | import fi.dy.masa.litematica.util.PositionUtils; 12 | import fi.dy.masa.litematica.util.ReplaceBehavior; 13 | import fi.dy.masa.malilib.gui.Message; 14 | import fi.dy.masa.malilib.util.InfoUtils; 15 | import fi.dy.masa.malilib.util.IntBoundingBox; 16 | import fi.dy.masa.malilib.util.LayerRange; 17 | import fi.dy.masa.malilib.util.NBTUtils; 18 | import net.earthcomputer.litemoretica.network.PacketSplitter; 19 | import net.earthcomputer.litemoretica.network.UploadChunkPacket; 20 | import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; 21 | import net.minecraft.block.Block; 22 | import net.minecraft.block.BlockState; 23 | import net.minecraft.fluid.Fluid; 24 | import net.minecraft.nbt.NbtCompound; 25 | import net.minecraft.nbt.NbtElement; 26 | import net.minecraft.util.BlockMirror; 27 | import net.minecraft.util.BlockRotation; 28 | import net.minecraft.util.math.*; 29 | import net.minecraft.world.tick.OrderedTick; 30 | 31 | import java.util.*; 32 | 33 | public class PasteHandlerClient extends TaskPasteSchematicPerChunkBase { 34 | private final ArrayListMultimap placementsPerChunk = ArrayListMultimap.create(); 35 | 36 | public PasteHandlerClient(Collection placements, LayerRange range, boolean changedBlocksOnly) { 37 | super(placements, range, changedBlocksOnly); 38 | } 39 | 40 | public static boolean canRun() { 41 | return LitemoreticaClient.HAS_NETWORKING && ClientPlayNetworking.canSend(UploadChunkPacket.TYPE.id()); 42 | } 43 | 44 | @Override 45 | public boolean canExecute() { 46 | return super.canExecute() && canRun(); 47 | } 48 | 49 | @Override 50 | protected void onChunkAddedForHandling(ChunkPos pos, SchematicPlacement placement) { 51 | super.onChunkAddedForHandling(pos, placement); 52 | placementsPerChunk.put(pos, placement); 53 | } 54 | 55 | @Override 56 | public boolean execute() { 57 | if (ignoreBlocks && ignoreEntities) { 58 | return true; 59 | } 60 | 61 | sortChunkList(); 62 | 63 | for (int chunkIndex = 0; chunkIndex < pendingChunks.size(); chunkIndex++) { 64 | ChunkPos pos = pendingChunks.get(chunkIndex); 65 | if (canProcessChunk(pos) && processChunk(pos)) { 66 | pendingChunks.remove(chunkIndex); 67 | break; 68 | } 69 | } 70 | 71 | if (pendingChunks.isEmpty()) { 72 | finished = true; 73 | return true; 74 | } 75 | 76 | updateInfoHudLines(); 77 | 78 | return false; 79 | } 80 | 81 | @Override 82 | protected boolean processChunk(ChunkPos pos) { 83 | for (SchematicPlacement placementMain : placementsPerChunk.removeAll(pos)) { 84 | placementMain.getBoxesWithinChunk(pos.x, pos.z).forEach((regionName, box) -> { 85 | processSubPlacement(placementMain, regionName, box); 86 | }); 87 | } 88 | return true; 89 | } 90 | 91 | @Override 92 | protected void onStop() { 93 | if (finished) { 94 | InfoUtils.showGuiOrActionBarMessage(Message.MessageType.SUCCESS, "litematica.message.schematic_pasted"); 95 | } else { 96 | InfoUtils.showGuiOrActionBarMessage(Message.MessageType.ERROR, "litematica.message.error.schematic_paste_failed"); 97 | } 98 | 99 | InfoHud.getInstance().removeInfoHudRenderer(this, false); 100 | 101 | super.onStop(); 102 | } 103 | 104 | private static void processSubPlacement(SchematicPlacement placementMain, String regionName, IntBoundingBox box) { 105 | SubRegionPlacement placementSub = placementMain.getRelativeSubRegionPlacement(regionName); 106 | Vec3i regionSize = placementMain.getSchematic().getAreaSize(regionName); 107 | LitematicaBlockStateContainer container = placementMain.getSchematic().getSubRegionContainer(regionName); 108 | Map blockEntityMap = placementMain.getSchematic().getBlockEntityMapForRegion(regionName); 109 | List entityList = placementMain.getSchematic().getEntityListForRegion(regionName); 110 | Map> scheduledBlockTicks = placementMain.getSchematic().getScheduledBlockTicksForRegion(regionName); 111 | Map> scheduledFluidTicks = placementMain.getSchematic().getScheduledFluidTicksForRegion(regionName); 112 | 113 | if (placementSub == null || regionSize == null || container == null) { 114 | return; 115 | } 116 | 117 | BlockRotation rotationCombined = placementMain.getRotation().rotate(placementSub.getRotation()); 118 | BlockMirror mirrorMain = placementMain.getMirror(); 119 | BlockMirror mirrorSub = placementSub.getMirror(); 120 | boolean ignoreInventories = Configs.Generic.PASTE_IGNORE_INVENTORY.getBooleanValue(); 121 | 122 | if (mirrorSub != BlockMirror.NONE && (placementMain.getRotation() == BlockRotation.CLOCKWISE_90 || placementMain.getRotation() == BlockRotation.COUNTERCLOCKWISE_90)) { 123 | mirrorSub = mirrorSub == BlockMirror.FRONT_BACK ? BlockMirror.LEFT_RIGHT : BlockMirror.FRONT_BACK; 124 | } 125 | 126 | BlockPos regionPos = placementSub.getPos(); 127 | 128 | // These are the untransformed relative positions 129 | BlockPos posEndRel = (new BlockPos(PositionUtils.getRelativeEndPositionFromAreaSize(regionSize))).add(regionPos); 130 | BlockPos posMinRel = PositionUtils.getMinCorner(regionPos, posEndRel); 131 | 132 | // The transformed sub-region origin position 133 | BlockPos regionPosTransformed = PositionUtils.getTransformedBlockPos(regionPos, mirrorMain, placementMain.getRotation()); 134 | 135 | int posMinRelMinusRegX = posMinRel.getX() - regionPos.getX(); 136 | int posMinRelMinusRegY = posMinRel.getY() - regionPos.getY(); 137 | int posMinRelMinusRegZ = posMinRel.getZ() - regionPos.getZ(); 138 | 139 | UploadChunkPacket.ReplaceBehavior replaceBehavior = switch ((ReplaceBehavior) Configs.Generic.PASTE_REPLACE_BEHAVIOR.getOptionListValue()) { 140 | case NONE -> UploadChunkPacket.ReplaceBehavior.NONE; 141 | case ALL -> UploadChunkPacket.ReplaceBehavior.ALL; 142 | case WITH_NON_AIR -> UploadChunkPacket.ReplaceBehavior.WITH_NON_AIR; 143 | }; 144 | 145 | BlockPos minPos = new BlockPos(box.minX, box.minY, box.minZ); 146 | BlockPos maxPos = new BlockPos(box.maxX, box.maxY, box.maxZ); 147 | 148 | BlockState[] blockData = new BlockState[(box.maxX - box.minX + 1) * (box.maxY - box.minY + 1) * (box.maxZ - box.minZ + 1)]; 149 | List blockEntities = new ArrayList<>(); 150 | List> blockTicks = new ArrayList<>(); 151 | List> fluidTicks = new ArrayList<>(); 152 | 153 | BlockPos.Mutable mutablePos = new BlockPos.Mutable(); 154 | int blockIndex = 0; 155 | for (int y = box.minY; y <= box.maxY; y++) { 156 | for (int z = box.minZ; z <= box.maxZ; z++) { 157 | for (int x = box.minX; x <= box.maxX; x++) { 158 | mutablePos.set( 159 | x - placementMain.getOrigin().getX() - regionPosTransformed.getX(), 160 | y - placementMain.getOrigin().getY() - regionPosTransformed.getY(), 161 | z - placementMain.getOrigin().getZ() - regionPosTransformed.getZ()); 162 | mutablePos.set(PositionUtils.getReverseTransformedBlockPos(mutablePos, placementSub.getMirror(), placementSub.getRotation())); 163 | mutablePos.set(PositionUtils.getReverseTransformedBlockPos(mutablePos, placementMain.getMirror(), placementMain.getRotation())); 164 | mutablePos.set(mutablePos.getX() - posMinRelMinusRegX, mutablePos.getY() - posMinRelMinusRegY, mutablePos.getZ() - posMinRelMinusRegZ); 165 | 166 | BlockState state = container.get(mutablePos.getX(), mutablePos.getY(), mutablePos.getZ()); 167 | if (mirrorMain != BlockMirror.NONE) { 168 | state = state.mirror(mirrorMain); 169 | } 170 | if (mirrorSub != BlockMirror.NONE) { 171 | state = state.mirror(mirrorSub); 172 | } 173 | if (rotationCombined != BlockRotation.NONE) { 174 | state = state.rotate(rotationCombined); 175 | } 176 | blockData[blockIndex++] = state; 177 | 178 | if (blockEntityMap != null) { 179 | NbtCompound beNbt = blockEntityMap.get(mutablePos); 180 | if (beNbt != null) { 181 | beNbt = beNbt.copy(); 182 | beNbt.putInt("x", x); 183 | beNbt.putInt("y", y); 184 | beNbt.putInt("z", z); 185 | if (ignoreInventories) { 186 | beNbt.remove("Items"); 187 | } 188 | blockEntities.add(beNbt); 189 | } 190 | } 191 | 192 | if (scheduledBlockTicks != null) { 193 | OrderedTick blockTick = scheduledBlockTicks.get(mutablePos); 194 | if (blockTick != null) { 195 | blockTicks.add(new OrderedTick<>(blockTick.type(), new BlockPos(x, y, z), blockTick.triggerTick(), blockTick.priority(), blockTick.subTickOrder())); 196 | } 197 | } 198 | if (scheduledFluidTicks != null) { 199 | OrderedTick fluidTick = scheduledFluidTicks.get(mutablePos); 200 | if (fluidTick != null) { 201 | fluidTicks.add(new OrderedTick<>(fluidTick.type(), new BlockPos(x, y, z), fluidTick.triggerTick(), fluidTick.priority(), fluidTick.subTickOrder())); 202 | } 203 | } 204 | } 205 | } 206 | } 207 | 208 | List entities = new ArrayList<>(); 209 | if (entityList != null) { 210 | for (LitematicaSchematic.EntityInfo info : entityList) { 211 | Vec3d entityPos = info.posVec; 212 | entityPos = PositionUtils.getTransformedPosition(entityPos, placementMain.getMirror(), placementMain.getRotation()); 213 | entityPos = PositionUtils.getTransformedPosition(entityPos, placementSub.getMirror(), placementSub.getRotation()); 214 | entityPos = entityPos.add( 215 | regionPosTransformed.getX() + placementMain.getOrigin().getX(), 216 | regionPosTransformed.getY() + placementMain.getOrigin().getY(), 217 | regionPosTransformed.getZ() + placementMain.getOrigin().getZ()); 218 | if (box.containsPos(BlockPos.ofFloored(entityPos))) { 219 | NbtCompound entityTag = info.nbt.copy(); 220 | NBTUtils.writeEntityPositionToTag(entityPos, entityTag); 221 | if (entityTag.contains("TileX", NbtElement.NUMBER_TYPE)) { 222 | entityTag.putInt("TileX", MathHelper.floor(entityPos.getX())); 223 | } 224 | if (entityTag.contains("TileY", NbtElement.NUMBER_TYPE)) { 225 | entityTag.putInt("TileY", MathHelper.floor(entityPos.getY())); 226 | } 227 | if (entityTag.contains("TileZ", NbtElement.NUMBER_TYPE)) { 228 | entityTag.putInt("TileZ", MathHelper.floor(entityPos.getZ())); 229 | } 230 | entities.add(entityTag); 231 | } 232 | } 233 | } 234 | 235 | UploadChunkPacket packet = new UploadChunkPacket(replaceBehavior, minPos, maxPos, blockData, blockEntities, entities, mirrorMain, mirrorSub, rotationCombined, blockTicks, fluidTicks); 236 | PacketSplitter.sendToServer(packet); 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/litemoretica/mixin/LitemoreticaMixinPlugin.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.litemoretica.mixin; 2 | 3 | import net.fabricmc.api.EnvType; 4 | import net.fabricmc.loader.api.FabricLoader; 5 | import net.fabricmc.loader.api.Version; 6 | import net.fabricmc.loader.api.VersionParsingException; 7 | import org.jetbrains.annotations.Nullable; 8 | import org.objectweb.asm.tree.ClassNode; 9 | import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; 10 | import org.spongepowered.asm.mixin.extensibility.IMixinInfo; 11 | 12 | import java.util.List; 13 | import java.util.Set; 14 | 15 | public class LitemoreticaMixinPlugin implements IMixinConfigPlugin { 16 | private boolean enabled; 17 | @Nullable 18 | private Version litematicaVersion; 19 | 20 | private static final Version V0_15_3; 21 | static { 22 | try { 23 | V0_15_3 = Version.parse("0.15.3"); 24 | } catch (VersionParsingException e) { 25 | throw new AssertionError(e); 26 | } 27 | } 28 | 29 | @Override 30 | public void onLoad(String mixinPackage) { 31 | litematicaVersion = FabricLoader.getInstance().getModContainer("litematica").map(mod -> mod.getMetadata().getVersion()).orElse(null); 32 | enabled = isEnabled(mixinPackage.contains("client")); 33 | } 34 | 35 | private boolean isEnabled(boolean client) { 36 | if (client) { 37 | if (FabricLoader.getInstance().getEnvironmentType() != EnvType.CLIENT) { 38 | return false; 39 | } 40 | if (litematicaVersion == null) { 41 | throw new IllegalStateException("litemoretica requires litematica on the client, but it is not installed. Please install litematica"); 42 | } 43 | return true; 44 | } 45 | 46 | return true; 47 | } 48 | 49 | @Override 50 | public String getRefMapperConfig() { 51 | return null; 52 | } 53 | 54 | @Override 55 | public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { 56 | if (!enabled) { 57 | return false; 58 | } 59 | 60 | if ("net.earthcomputer.litemoretica.mixin.client.RayTraceUtilsMixin_ShapeContextFix".equals(mixinClassName) 61 | && litematicaVersion != null 62 | && litematicaVersion.compareTo(V0_15_3) >= 0) { 63 | return false; 64 | } 65 | 66 | return true; 67 | } 68 | 69 | @Override 70 | public void acceptTargets(Set myTargets, Set otherTargets) { 71 | } 72 | 73 | @Override 74 | public List getMixins() { 75 | return null; 76 | } 77 | 78 | @Override 79 | public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { 80 | } 81 | 82 | @Override 83 | public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/litemoretica/mixin/client/ClientPlayNetworkHandlerMixin_PacketSplitter.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.litemoretica.mixin.client; 2 | 3 | import net.earthcomputer.litemoretica.network.PacketSplitter; 4 | import net.minecraft.client.network.ClientPlayNetworkHandler; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.Unique; 7 | 8 | @Mixin(ClientPlayNetworkHandler.class) 9 | public class ClientPlayNetworkHandlerMixin_PacketSplitter implements PacketSplitter.NetHandlerExt { 10 | @Unique 11 | private final PacketSplitter litemoretica_packetSplitter = new PacketSplitter(); 12 | 13 | @Override 14 | public PacketSplitter litemoretica_getPacketSplitter() { 15 | return litemoretica_packetSplitter; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/litemoretica/mixin/client/ClientPlayerInteractionManagerMixin_EasyPlaceFix.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.litemoretica.mixin.client; 2 | 3 | import fi.dy.masa.litematica.config.Configs; 4 | import net.earthcomputer.litemoretica.client.EasyPlaceFix; 5 | import net.minecraft.client.MinecraftClient; 6 | import net.minecraft.client.network.ClientPlayerEntity; 7 | import net.minecraft.client.network.ClientPlayerInteractionManager; 8 | import net.minecraft.util.ActionResult; 9 | import net.minecraft.util.Hand; 10 | import net.minecraft.util.hit.BlockHitResult; 11 | import org.spongepowered.asm.mixin.Final; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.Shadow; 14 | import org.spongepowered.asm.mixin.injection.At; 15 | import org.spongepowered.asm.mixin.injection.Inject; 16 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 17 | 18 | @Mixin(ClientPlayerInteractionManager.class) 19 | public class ClientPlayerInteractionManagerMixin_EasyPlaceFix { 20 | @Shadow @Final private MinecraftClient client; 21 | 22 | @Inject(method = "interactBlock", at = @At("HEAD"), cancellable = true) 23 | private void onInteractBlock(ClientPlayerEntity player, Hand hand, BlockHitResult hitResult, CallbackInfoReturnable cir) { 24 | if (Configs.Generic.EASY_PLACE_MODE.getBooleanValue() && !EasyPlaceFix.isPlacingWithEasyPlace) { 25 | if (EasyPlaceFix.handleEasyPlaceRestriction(client)) { 26 | cir.setReturnValue(ActionResult.FAIL); 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/litemoretica/mixin/client/ConfigsGenericMixin.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.litemoretica.mixin.client; 2 | 3 | import fi.dy.masa.litematica.config.Configs; 4 | import net.earthcomputer.litemoretica.client.LitemoreticaConfigs; 5 | import org.apache.commons.lang3.ArrayUtils; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.ModifyArg; 9 | 10 | @Mixin(value = Configs.Generic.class, remap = false) 11 | public class ConfigsGenericMixin { 12 | @ModifyArg(method = "", at = @At(value = "INVOKE", target = "Lcom/google/common/collect/ImmutableList;of(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;[Ljava/lang/Object;)Lcom/google/common/collect/ImmutableList;")) 13 | private static Object[] modifyConfigs(Object[] configs) { 14 | return ArrayUtils.addAll(configs, (Object[]) LitemoreticaConfigs.getExtraGenericConfigs()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/litemoretica/mixin/client/HotkeysMixin.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.litemoretica.mixin.client; 2 | 3 | import fi.dy.masa.litematica.config.Hotkeys; 4 | import net.earthcomputer.litemoretica.client.LitemoreticaHotkeys; 5 | import org.apache.commons.lang3.ArrayUtils; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.ModifyArg; 9 | 10 | @Mixin(value = Hotkeys.class, remap = false) 11 | public class HotkeysMixin { 12 | @ModifyArg(method = "", at = @At(value = "INVOKE", target = "Lcom/google/common/collect/ImmutableList;of(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;[Ljava/lang/Object;)Lcom/google/common/collect/ImmutableList;")) 13 | private static Object[] modifyHotkeys(Object[] hotkeys) { 14 | return ArrayUtils.addAll(hotkeys, (Object[]) LitemoreticaHotkeys.getExtraHotkeys()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/litemoretica/mixin/client/PlacementHandlerAccessor.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.litemoretica.mixin.client; 2 | 3 | import com.google.common.collect.ImmutableSet; 4 | import fi.dy.masa.litematica.util.PlacementHandler; 5 | import net.minecraft.state.property.Property; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.Mutable; 8 | import org.spongepowered.asm.mixin.gen.Accessor; 9 | 10 | @Mixin(value = PlacementHandler.class, remap = false) 11 | public interface PlacementHandlerAccessor { 12 | @Accessor("WHITELISTED_PROPERTIES") 13 | @Mutable 14 | static void setWhitelistedProperties(ImmutableSet> whitelistedProperties) { 15 | throw new UnsupportedOperationException(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/litemoretica/mixin/client/PlacementHandlerMixin_EasyPlaceProtocol.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.litemoretica.mixin.client; 2 | 3 | import fi.dy.masa.litematica.util.EasyPlaceProtocol; 4 | import fi.dy.masa.litematica.util.PlacementHandler; 5 | import net.earthcomputer.litemoretica.client.EasyPlaceProtocolClient; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Inject; 9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 10 | 11 | @Mixin(value = PlacementHandler.class, remap = false) 12 | public class PlacementHandlerMixin_EasyPlaceProtocol { 13 | @Inject(method = "getEffectiveProtocolVersion", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MinecraftClient;isInSingleplayer()Z", remap = true), cancellable = true) 14 | private static void allowV3OnLitemoreticaServer(CallbackInfoReturnable cir) { 15 | if (EasyPlaceProtocolClient.serverHasV3Protocol) { 16 | cir.setReturnValue(EasyPlaceProtocol.V3); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/litemoretica/mixin/client/RayTraceUtilsMixin_ShapeContextFix.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.litemoretica.mixin.client; 2 | 3 | import fi.dy.masa.litematica.util.RayTraceUtils; 4 | import net.minecraft.block.BlockState; 5 | import net.minecraft.block.ShapeContext; 6 | import net.minecraft.entity.Entity; 7 | import net.minecraft.util.hit.BlockHitResult; 8 | import net.minecraft.util.hit.HitResult; 9 | import net.minecraft.util.math.BlockPos; 10 | import net.minecraft.util.shape.VoxelShape; 11 | import net.minecraft.world.BlockView; 12 | import net.minecraft.world.World; 13 | import org.jetbrains.annotations.Nullable; 14 | import org.spongepowered.asm.mixin.Mixin; 15 | import org.spongepowered.asm.mixin.Unique; 16 | import org.spongepowered.asm.mixin.injection.At; 17 | import org.spongepowered.asm.mixin.injection.Inject; 18 | import org.spongepowered.asm.mixin.injection.Redirect; 19 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 20 | 21 | import java.lang.ref.WeakReference; 22 | 23 | @Mixin(value = RayTraceUtils.class, remap = false) 24 | public class RayTraceUtilsMixin_ShapeContextFix { 25 | @Unique 26 | @Nullable 27 | private static WeakReference litemoretica_currentEntity; 28 | 29 | @Inject(method = "traceToSchematicWorld", at = @At(value = "INVOKE", target = "Lfi/dy/masa/litematica/util/RayTraceUtils;rayTraceBlocks(Lnet/minecraft/world/World;Lnet/minecraft/util/math/Vec3d;Lnet/minecraft/util/math/Vec3d;Lnet/minecraft/world/RaycastContext$FluidHandling;ZZZI)Lnet/minecraft/util/hit/BlockHitResult;", remap = true)) 30 | private static void traceToSchematicWorld_preRayTraceBlocks(Entity entity, double range, boolean respectRenderRange, boolean targetFluids, CallbackInfoReturnable cir) { 31 | litemoretica_currentEntity = new WeakReference<>(entity); 32 | } 33 | 34 | @Inject(method = "traceToSchematicWorld", at = @At(value = "INVOKE", target = "Lfi/dy/masa/litematica/util/RayTraceUtils;rayTraceBlocks(Lnet/minecraft/world/World;Lnet/minecraft/util/math/Vec3d;Lnet/minecraft/util/math/Vec3d;Lnet/minecraft/world/RaycastContext$FluidHandling;ZZZI)Lnet/minecraft/util/hit/BlockHitResult;", shift = At.Shift.AFTER, remap = true)) 35 | private static void traceToSchematicWorld_postRayTraceBlocks(CallbackInfoReturnable cir) { 36 | litemoretica_currentEntity = null; 37 | } 38 | 39 | @Inject(method = "getFurthestSchematicWorldBlockBeforeVanilla", at = @At(value = "INVOKE", target = "Lfi/dy/masa/litematica/util/RayTraceUtils;rayTraceBlocksToList(Lnet/minecraft/world/World;Lnet/minecraft/util/math/Vec3d;Lnet/minecraft/util/math/Vec3d;Lnet/minecraft/world/RaycastContext$FluidHandling;ZZZI)Ljava/util/List;", remap = true)) 40 | private static void getFurthestSchematicWorldBlockBeforeVanilla_preRayTraceBlocksToList(World worldClient, Entity entity, double maxRange, boolean requireVanillaBlockBehind, CallbackInfoReturnable cir) { 41 | litemoretica_currentEntity = new WeakReference<>(entity); 42 | } 43 | 44 | @Inject(method = "getFurthestSchematicWorldBlockBeforeVanilla", at = @At(value = "INVOKE", target = "Lfi/dy/masa/litematica/util/RayTraceUtils;rayTraceBlocksToList(Lnet/minecraft/world/World;Lnet/minecraft/util/math/Vec3d;Lnet/minecraft/util/math/Vec3d;Lnet/minecraft/world/RaycastContext$FluidHandling;ZZZI)Ljava/util/List;", shift = At.Shift.AFTER, remap = true)) 45 | private static void getFurthestSchematicWorldBlockBeforeVanilla_postRayTraceBlocksToList(CallbackInfoReturnable cir) { 46 | litemoretica_currentEntity = null; 47 | } 48 | 49 | @Inject(method = "getRayTraceFromEntity", at = @At(value = "INVOKE", target = "Lfi/dy/masa/litematica/util/RayTraceUtils;rayTraceBlocks(Lnet/minecraft/world/World;Lnet/minecraft/util/math/Vec3d;Lnet/minecraft/util/math/Vec3d;Lnet/minecraft/world/RaycastContext$FluidHandling;ZZZI)Lnet/minecraft/util/hit/BlockHitResult;", remap = true)) 50 | private static void getRayTraceFromEntity_preRayTraceBlocks(World world, Entity entity, boolean useLiquids, double range, CallbackInfoReturnable cir) { 51 | litemoretica_currentEntity = new WeakReference<>(entity); 52 | } 53 | 54 | @Inject(method = "getRayTraceFromEntity", at = @At(value = "INVOKE", target = "Lfi/dy/masa/litematica/util/RayTraceUtils;rayTraceBlocks(Lnet/minecraft/world/World;Lnet/minecraft/util/math/Vec3d;Lnet/minecraft/util/math/Vec3d;Lnet/minecraft/world/RaycastContext$FluidHandling;ZZZI)Lnet/minecraft/util/hit/BlockHitResult;", shift = At.Shift.AFTER, remap = true)) 55 | private static void getRayTraceFromEntity_postRayTraceBlocks(CallbackInfoReturnable cir) { 56 | litemoretica_currentEntity = null; 57 | } 58 | 59 | @Redirect(method = {"traceFirstStep", "traceLoopSteps"}, at = @At(value = "INVOKE", target = "Lnet/minecraft/block/BlockState;getOutlineShape(Lnet/minecraft/world/BlockView;Lnet/minecraft/util/math/BlockPos;)Lnet/minecraft/util/shape/VoxelShape;", remap = true)) 60 | private static VoxelShape redirectGetOutlineShape(BlockState instance, BlockView blockView, BlockPos blockPos) { 61 | Entity currentEntity; 62 | if (litemoretica_currentEntity != null && (currentEntity = litemoretica_currentEntity.get()) != null) { 63 | return instance.getOutlineShape(blockView, blockPos, ShapeContext.of(currentEntity)); 64 | } else { 65 | return instance.getOutlineShape(blockView, blockPos); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/litemoretica/mixin/client/SchematicPlacementManagerMixin_PasteHandler.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.litemoretica.mixin.client; 2 | 3 | import fi.dy.masa.litematica.config.Configs; 4 | import fi.dy.masa.litematica.data.DataManager; 5 | import fi.dy.masa.litematica.scheduler.TaskScheduler; 6 | import fi.dy.masa.litematica.schematic.placement.SchematicPlacement; 7 | import fi.dy.masa.litematica.schematic.placement.SchematicPlacementManager; 8 | import fi.dy.masa.malilib.gui.Message; 9 | import fi.dy.masa.malilib.util.InfoUtils; 10 | import net.earthcomputer.litemoretica.client.PasteHandlerClient; 11 | import net.minecraft.client.MinecraftClient; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.injection.At; 14 | import org.spongepowered.asm.mixin.injection.Inject; 15 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 16 | 17 | import java.util.Collections; 18 | 19 | @Mixin(value = SchematicPlacementManager.class, remap = false) 20 | public class SchematicPlacementManagerMixin_PasteHandler { 21 | @Inject(method = "pastePlacementToWorld(Lfi/dy/masa/litematica/schematic/placement/SchematicPlacement;ZZLnet/minecraft/client/MinecraftClient;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MinecraftClient;isIntegratedServerRunning()Z", remap = true), remap = true, cancellable = true) 22 | private void useOurPasteHandling(SchematicPlacement schematicPlacement, boolean changedBlocksOnly, boolean printMessage, MinecraftClient mc, CallbackInfo ci) { 23 | if (PasteHandlerClient.canRun()) { 24 | PasteHandlerClient task = new PasteHandlerClient(Collections.singletonList(schematicPlacement), DataManager.getRenderLayerRange(), changedBlocksOnly); 25 | TaskScheduler.getInstanceClient().scheduleTask(task, Configs.Generic.COMMAND_TASK_INTERVAL.getIntegerValue()); 26 | if (printMessage) { 27 | InfoUtils.showGuiOrActionBarMessage(Message.MessageType.INFO, "litematica.message.scheduled_task_added"); 28 | } 29 | ci.cancel(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/litemoretica/mixin/client/SchematicUtilsAccessor.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.litemoretica.mixin.client; 2 | 3 | import fi.dy.masa.litematica.util.SchematicUtils; 4 | import net.minecraft.block.BlockState; 5 | import net.minecraft.util.math.BlockPos; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.gen.Invoker; 8 | 9 | @Mixin(value = SchematicUtils.class, remap = false) 10 | public interface SchematicUtilsAccessor { 11 | @Invoker 12 | static boolean invokeSetTargetedSchematicBlockState(BlockPos pos, BlockState state) { 13 | throw new UnsupportedOperationException(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/litemoretica/mixin/client/TaskCountBlocksPlacementMixin_MaterialListFeature.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.litemoretica.mixin.client; 2 | 3 | import com.llamalad7.mixinextras.sugar.Local; 4 | import fi.dy.masa.litematica.materials.IMaterialList; 5 | import fi.dy.masa.litematica.scheduler.tasks.TaskCountBlocksBase; 6 | import fi.dy.masa.litematica.scheduler.tasks.TaskCountBlocksPlacement; 7 | import net.earthcomputer.litemoretica.client.LitemoreticaConfigs; 8 | import net.minecraft.block.BlockState; 9 | import net.minecraft.util.math.BlockPos; 10 | import org.spongepowered.asm.mixin.Mixin; 11 | import org.spongepowered.asm.mixin.Unique; 12 | import org.spongepowered.asm.mixin.injection.At; 13 | import org.spongepowered.asm.mixin.injection.Inject; 14 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 15 | 16 | @Mixin(value = TaskCountBlocksPlacement.class, remap = false) 17 | public abstract class TaskCountBlocksPlacementMixin_MaterialListFeature extends TaskCountBlocksBase { 18 | @Unique private boolean ignoreState; 19 | 20 | protected TaskCountBlocksPlacementMixin_MaterialListFeature(IMaterialList materialList, String nameOnHud) { 21 | super(materialList, nameOnHud); 22 | } 23 | 24 | @Inject(method = "", at = @At(value = "RETURN")) 25 | private void onConstructor(CallbackInfo info) { 26 | this.ignoreState = LitemoreticaConfigs.MATERIAL_LIST_IGNORE_BLOCK_STATE.getBooleanValue(); 27 | } 28 | 29 | @Inject(method = "countAtPosition", at = @At(value = "FIELD", target = "Lfi/dy/masa/litematica/scheduler/tasks/TaskCountBlocksPlacement;countsMissing:Lit/unimi/dsi/fastutil/objects/Object2IntOpenHashMap;"), cancellable = true) 30 | protected void countAtPosition(BlockPos pos, CallbackInfo ci, @Local(name = "stateSchematic") BlockState stateSchematic, @Local(name = "stateClient") BlockState stateClient) { 31 | if (!stateClient.isAir() && ignoreState && stateClient.getBlock() == stateSchematic.getBlock()) { 32 | ci.cancel(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/litemoretica/mixin/client/WorldUtilsAccessor.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.litemoretica.mixin.client; 2 | 3 | import fi.dy.masa.litematica.util.WorldUtils; 4 | import net.minecraft.client.MinecraftClient; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Invoker; 7 | 8 | @Mixin(WorldUtils.class) 9 | public interface WorldUtilsAccessor { 10 | @Invoker 11 | static boolean invokePlacementRestrictionInEffect(MinecraftClient mc) { 12 | throw new UnsupportedOperationException(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/litemoretica/mixin/client/WorldUtilsMixin_EasyPlaceFix.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.litemoretica.mixin.client; 2 | 3 | import fi.dy.masa.litematica.util.WorldUtils; 4 | import fi.dy.masa.malilib.util.LayerRange; 5 | import net.earthcomputer.litemoretica.client.EasyPlaceFix; 6 | import net.minecraft.block.BlockState; 7 | import net.minecraft.client.MinecraftClient; 8 | import net.minecraft.item.ItemPlacementContext; 9 | import net.minecraft.item.ItemStack; 10 | import net.minecraft.util.ActionResult; 11 | import net.minecraft.util.hit.BlockHitResult; 12 | import net.minecraft.util.hit.HitResult; 13 | import net.minecraft.util.math.BlockPos; 14 | import net.minecraft.world.World; 15 | import org.spongepowered.asm.mixin.Mixin; 16 | import org.spongepowered.asm.mixin.injection.At; 17 | import org.spongepowered.asm.mixin.injection.Inject; 18 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 19 | import org.spongepowered.asm.mixin.injection.callback.LocalCapture; 20 | 21 | @Mixin(value = WorldUtils.class, remap = false) 22 | public class WorldUtilsMixin_EasyPlaceFix { 23 | @Inject(method = "doEasyPlaceAction", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerInteractionManager;interactBlock(Lnet/minecraft/client/network/ClientPlayerEntity;Lnet/minecraft/util/Hand;Lnet/minecraft/util/hit/BlockHitResult;)Lnet/minecraft/util/ActionResult;", remap = true), require = 2) 24 | private static void preInteractBlock(CallbackInfoReturnable cir) { 25 | EasyPlaceFix.isPlacingWithEasyPlace = true; 26 | } 27 | 28 | @Inject(method = "doEasyPlaceAction", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerInteractionManager;interactBlock(Lnet/minecraft/client/network/ClientPlayerEntity;Lnet/minecraft/util/Hand;Lnet/minecraft/util/hit/BlockHitResult;)Lnet/minecraft/util/ActionResult;", shift = At.Shift.AFTER, remap = true), require = 2) 29 | private static void postInteractBlock(CallbackInfoReturnable cir) { 30 | EasyPlaceFix.isPlacingWithEasyPlace = false; 31 | } 32 | 33 | @Inject(method = "placementRestrictionInEffect", at = @At(value = "INVOKE", target = "Lfi/dy/masa/litematica/materials/MaterialCache;getInstance()Lfi/dy/masa/litematica/materials/MaterialCache;"), cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD) 34 | private static void stopEasyPlaceWhenBlockAlreadyCorrect(MinecraftClient mc, CallbackInfoReturnable cir, HitResult trace, ItemStack stack, BlockHitResult blockHitResult, ItemPlacementContext ctx, BlockPos pos, BlockState stateClient, World worldSchematic, LayerRange range, boolean schematicHasAir, BlockState stateSchematic) { 35 | if (stateClient == stateSchematic) { 36 | cir.setReturnValue(Boolean.TRUE); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/litemoretica/mixin/server/BlockItemMixin_EasyPlaceProtocol.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.litemoretica.mixin.server; 2 | 3 | import net.earthcomputer.litemoretica.server.EasyPlaceProtocolServer; 4 | import net.minecraft.block.Block; 5 | import net.minecraft.block.BlockState; 6 | import net.minecraft.item.BlockItem; 7 | import net.minecraft.item.ItemPlacementContext; 8 | import net.minecraft.server.network.ServerPlayerEntity; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.Shadow; 11 | import org.spongepowered.asm.mixin.injection.At; 12 | import org.spongepowered.asm.mixin.injection.Inject; 13 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 14 | 15 | @Mixin(value = BlockItem.class, priority = 900) // lower priority than litematica's mixin to inject before it 16 | public abstract class BlockItemMixin_EasyPlaceProtocol { 17 | @Shadow public abstract Block getBlock(); 18 | 19 | @Shadow protected abstract boolean canPlace(ItemPlacementContext context, BlockState state); 20 | 21 | @Inject(method = "getPlacementState", at = @At("HEAD"), cancellable = true) 22 | private void applyEasyPlaceProtocolV3(ItemPlacementContext ctx, CallbackInfoReturnable cir) { 23 | if (ctx.getPlayer() instanceof ServerPlayerEntity serverPlayer && EasyPlaceProtocolServer.getEasyPlaceProtocol(serverPlayer) == 3) { 24 | BlockState stateOrig = getBlock().getPlacementState(ctx); 25 | if (stateOrig != null) { 26 | BlockState newState = EasyPlaceProtocolServer.applyEasyPlaceProtocolV3(stateOrig, ctx); 27 | if (canPlace(ctx, newState)) { 28 | cir.setReturnValue(newState); 29 | } 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/litemoretica/mixin/server/ServerPlayNetworkHandlerMixin_EasyPlaceProtocol.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.litemoretica.mixin.server; 2 | 3 | import net.earthcomputer.litemoretica.server.EasyPlaceProtocolServer; 4 | import net.minecraft.server.network.ServerPlayNetworkHandler; 5 | import net.minecraft.util.math.Vec3d; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.Unique; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | import org.spongepowered.asm.mixin.injection.Redirect; 10 | 11 | @Mixin(value = ServerPlayNetworkHandler.class, priority = 900) // lower priority than carpet's and litematica's mixins so that the redirect is only applied if neither have it 12 | public class ServerPlayNetworkHandlerMixin_EasyPlaceProtocol implements EasyPlaceProtocolServer.NetworkHandlerExt { 13 | @Unique 14 | private int litemoretica_easyPlaceProtocol; 15 | 16 | @Override 17 | public int litemoretica_getEasyPlaceProtocol() { 18 | return litemoretica_easyPlaceProtocol; 19 | } 20 | 21 | @Override 22 | public void litemoretica_setEasyPlaceProtocol(int easyPlaceProtocol) { 23 | litemoretica_easyPlaceProtocol = easyPlaceProtocol; 24 | } 25 | 26 | @Redirect(method = "onPlayerInteractBlock", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/math/Vec3d;subtract(Lnet/minecraft/util/math/Vec3d;)Lnet/minecraft/util/math/Vec3d;"), require = 0) 27 | private Vec3d removeHitPosCheck(Vec3d hitVec, Vec3d blockCenter) { 28 | return Vec3d.ZERO; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/litemoretica/mixin/server/ServerPlayNetworkHandlerMixin_PacketSplitter.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.litemoretica.mixin.server; 2 | 3 | import net.earthcomputer.litemoretica.network.PacketSplitter; 4 | import net.minecraft.server.network.ServerPlayNetworkHandler; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.Unique; 7 | 8 | @Mixin(ServerPlayNetworkHandler.class) 9 | public class ServerPlayNetworkHandlerMixin_PacketSplitter implements PacketSplitter.NetHandlerExt { 10 | @Unique 11 | private final PacketSplitter litemoretica_packetSplitter = new PacketSplitter(); 12 | 13 | @Override 14 | public PacketSplitter litemoretica_getPacketSplitter() { 15 | return litemoretica_packetSplitter; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/litemoretica/network/InitEasyPlaceProtocolPacket.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.litemoretica.network; 2 | 3 | import com.google.common.collect.ImmutableSet; 4 | import net.minecraft.block.Block; 5 | import net.minecraft.network.PacketByteBuf; 6 | import net.minecraft.registry.Registries; 7 | import net.minecraft.state.property.Property; 8 | import net.minecraft.util.Identifier; 9 | 10 | import java.util.HashSet; 11 | import java.util.Set; 12 | 13 | public record InitEasyPlaceProtocolPacket(ImmutableSet> whitelistedProperties) { 14 | public static final Identifier TYPE = new Identifier("litemoretica", "init_easy_place"); 15 | 16 | public InitEasyPlaceProtocolPacket(PacketByteBuf buf) { 17 | this(readWhitelistedProperties(buf)); 18 | } 19 | 20 | public void write(PacketByteBuf buf) { 21 | writeWhitelistedProperties(buf, whitelistedProperties); 22 | } 23 | 24 | private static ImmutableSet> readWhitelistedProperties(PacketByteBuf buf) { 25 | int numProperties = buf.readVarInt(); 26 | ImmutableSet.Builder> properties = ImmutableSet.builderWithExpectedSize(numProperties); 27 | for (int i = 0; i < numProperties; i++) { 28 | Identifier blockId = buf.readIdentifier(); 29 | Block block = Registries.BLOCK.get(blockId); 30 | String propertyName = buf.readString(256); 31 | Property property = block.getStateManager().getProperty(propertyName); 32 | if (property != null) { 33 | properties.add(property); 34 | } 35 | } 36 | return properties.build(); 37 | } 38 | 39 | private static void writeWhitelistedProperties(PacketByteBuf buf, ImmutableSet> whitelistedProperties) { 40 | buf.writeVarInt(whitelistedProperties.size()); 41 | Set> propertiesToWrite = new HashSet<>(whitelistedProperties); 42 | for (Block block : Registries.BLOCK) { 43 | for (Property property : block.getStateManager().getProperties()) { 44 | if (propertiesToWrite.remove(property)) { 45 | buf.writeIdentifier(Registries.BLOCK.getId(block)); 46 | buf.writeString(property.getName(), 256); 47 | if (propertiesToWrite.isEmpty()) { 48 | return; 49 | } 50 | } 51 | } 52 | } 53 | throw new IllegalStateException("Found properties with no block containing them: " + propertiesToWrite); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/litemoretica/network/PacketSplitter.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.litemoretica.network; 2 | 3 | import io.netty.buffer.Unpooled; 4 | import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; 5 | import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; 6 | import net.minecraft.network.PacketByteBuf; 7 | import net.minecraft.server.network.ServerPlayNetworkHandler; 8 | import net.minecraft.server.network.ServerPlayerEntity; 9 | import net.minecraft.text.Text; 10 | import net.minecraft.util.Identifier; 11 | import net.minecraft.util.math.MathHelper; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | import java.util.function.BiConsumer; 15 | import java.util.function.Consumer; 16 | import java.util.function.Function; 17 | 18 | public final class PacketSplitter { 19 | @Nullable 20 | private Identifier currentlyReceiving; 21 | private int partsLeftToReceive; 22 | private PacketByteBuf currentBuf; 23 | 24 | public static void registerC2S(SplitPacketType type, BiConsumer handler) { 25 | ServerPlayNetworking.registerGlobalReceiver(type.id, (server, player, handler1, buf, responseSender) -> { 26 | ((NetHandlerExt) handler1).litemoretica_getPacketSplitter().handle(buf, type, packet -> handler.accept(packet, handler1), handler1::disconnect); 27 | }); 28 | } 29 | 30 | public static void registerS2C(SplitPacketType type, Consumer handler) { 31 | ClientPlayNetworking.registerGlobalReceiver(type.id, (client, handler1, buf, responseSender) -> { 32 | ((NetHandlerExt) handler1).litemoretica_getPacketSplitter().handle(buf, type, handler, handler1.getConnection()::disconnect); 33 | }); 34 | } 35 | 36 | public static void sendToServer(SplitPacket packet) { 37 | send(packet, Short.MAX_VALUE, ClientPlayNetworking::send); 38 | } 39 | 40 | public static void sendToClient(ServerPlayerEntity player, SplitPacket packet) { 41 | send(packet, 1048576, (id, buf) -> ServerPlayNetworking.send(player, id, buf)); 42 | } 43 | 44 | private static void send(SplitPacket packet, int batchSize, BiConsumer sender) { 45 | PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer()); 46 | try { 47 | packet.write(buf); 48 | int numPackets = MathHelper.ceilDiv(buf.writerIndex() + 5, batchSize); 49 | 50 | PacketByteBuf buf2 = new PacketByteBuf(Unpooled.buffer()); 51 | buf2.writeVarInt(numPackets); 52 | buf2.writeBytes(buf, Math.min(buf.readableBytes(), batchSize - 5)); 53 | sender.accept(packet.getType().id, buf2); 54 | while (buf.isReadable()) { 55 | buf2 = new PacketByteBuf(Unpooled.buffer()); 56 | buf2.writeBytes(buf, Math.min(buf.readableBytes(), batchSize)); 57 | sender.accept(packet.getType().id, buf2); 58 | } 59 | } finally { 60 | buf.release(); 61 | } 62 | } 63 | 64 | public void handle(PacketByteBuf buf, SplitPacketType type, Consumer handler, Consumer disconnecter) { 65 | if (currentlyReceiving == null) { 66 | partsLeftToReceive = buf.readVarInt() - 1; 67 | if (partsLeftToReceive == 0) { 68 | handler.accept(type.deserializer.apply(buf)); 69 | } else { 70 | currentBuf = new PacketByteBuf(Unpooled.buffer()); 71 | currentBuf.writeBytes(buf); 72 | currentlyReceiving = type.id; 73 | } 74 | } else { 75 | if (!currentlyReceiving.equals(type.id)) { 76 | disconnecter.accept(Text.literal("Invalid split packet")); 77 | } 78 | currentBuf.writeBytes(buf); 79 | if (--partsLeftToReceive == 0) { 80 | try { 81 | handler.accept(type.deserializer.apply(currentBuf)); 82 | } finally { 83 | currentlyReceiving = null; 84 | currentBuf.release(); 85 | currentBuf = null; 86 | } 87 | } 88 | } 89 | } 90 | 91 | // TODO: remove in 1.20 (replaced with FabricPacket) 92 | public interface SplitPacket { 93 | void write(PacketByteBuf buf); 94 | SplitPacketType getType(); 95 | } 96 | 97 | public record SplitPacketType(Identifier id, Function deserializer) { 98 | public static SplitPacketType create(Identifier id, Function deserializer) { 99 | return new SplitPacketType<>(id, deserializer); 100 | } 101 | } 102 | 103 | public interface NetHandlerExt { 104 | PacketSplitter litemoretica_getPacketSplitter(); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/litemoretica/network/SetEasyPlaceProtocolPacket.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.litemoretica.network; 2 | 3 | import net.minecraft.network.PacketByteBuf; 4 | import net.minecraft.util.Identifier; 5 | 6 | public record SetEasyPlaceProtocolPacket(int protocol) { 7 | public static final Identifier TYPE = new Identifier("litemoretica", "set_easy_place_protocol"); 8 | 9 | public SetEasyPlaceProtocolPacket(PacketByteBuf buf) { 10 | this(buf.readVarInt()); 11 | } 12 | 13 | public void write(PacketByteBuf buf) { 14 | buf.writeVarInt(protocol); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/litemoretica/network/UploadChunkPacket.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.litemoretica.network; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.google.common.collect.ImmutableMap; 5 | import io.netty.handler.codec.DecoderException; 6 | import it.unimi.dsi.fastutil.objects.Object2IntMap; 7 | import it.unimi.dsi.fastutil.objects.Object2IntOpenCustomHashMap; 8 | import net.minecraft.block.Block; 9 | import net.minecraft.block.BlockState; 10 | import net.minecraft.fluid.Fluid; 11 | import net.minecraft.nbt.NbtCompound; 12 | import net.minecraft.network.PacketByteBuf; 13 | import net.minecraft.registry.Registries; 14 | import net.minecraft.registry.Registry; 15 | import net.minecraft.state.property.Property; 16 | import net.minecraft.util.BlockMirror; 17 | import net.minecraft.util.BlockRotation; 18 | import net.minecraft.util.Identifier; 19 | import net.minecraft.util.Util; 20 | import net.minecraft.util.math.BlockPos; 21 | import net.minecraft.util.math.MathHelper; 22 | import net.minecraft.world.tick.OrderedTick; 23 | import net.minecraft.world.tick.TickPriority; 24 | 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | import java.util.Optional; 28 | 29 | public class UploadChunkPacket implements PacketSplitter.SplitPacket { 30 | public static final PacketSplitter.SplitPacketType TYPE = PacketSplitter.SplitPacketType.create(new Identifier("litemoretica", "upload_chunk"), UploadChunkPacket::new); 31 | 32 | public final ReplaceBehavior replaceBehavior; 33 | public final BlockPos minPos; 34 | public final BlockPos maxPos; 35 | public final BlockState[] blockData; 36 | public final NbtCompound[] blockEntities; 37 | public final NbtCompound[] entities; 38 | public final BlockMirror entityMirror1; 39 | public final BlockMirror entityMirror2; 40 | public final BlockRotation entityRotation; 41 | public final ImmutableList> scheduledBlockTicks; 42 | public final ImmutableList> scheduledFluidTicks; 43 | 44 | public UploadChunkPacket(ReplaceBehavior replaceBehavior, BlockPos minPos, BlockPos maxPos, BlockState[] blockData, List blockEntities, List entities, BlockMirror entityMirror1, BlockMirror entityMirror2, BlockRotation entityRotation, List> scheduledBlockTicks, List> scheduledFluidTicks) { 45 | this.replaceBehavior = replaceBehavior; 46 | this.minPos = minPos; 47 | this.maxPos = maxPos; 48 | this.blockData = blockData; 49 | this.blockEntities = blockEntities.toArray(new NbtCompound[0]); 50 | this.entities = entities.toArray(new NbtCompound[0]); 51 | this.entityMirror1 = entityMirror1; 52 | this.entityMirror2 = entityMirror2; 53 | this.entityRotation = entityRotation; 54 | this.scheduledBlockTicks = ImmutableList.copyOf(scheduledBlockTicks); 55 | this.scheduledFluidTicks = ImmutableList.copyOf(scheduledFluidTicks); 56 | } 57 | 58 | public UploadChunkPacket(PacketByteBuf buf) { 59 | this.replaceBehavior = buf.readEnumConstant(ReplaceBehavior.class); 60 | this.minPos = buf.readBlockPos(); 61 | this.maxPos = buf.readBlockPos(); 62 | 63 | List palette = buf.readList(UploadChunkPacket::readBlockState); 64 | if (palette.isEmpty()) { 65 | throw new DecoderException("upload_chunk block palette is empty"); 66 | } 67 | 68 | int numBlocks = (Math.abs((maxPos.getX() & 15) - (minPos.getX() & 15)) + 1) * (Math.abs(maxPos.getY() - minPos.getY()) + 1) * (Math.abs((maxPos.getZ() & 15) - (minPos.getZ() & 15)) + 1); 69 | int bitsPerBlock = Math.max(1, MathHelper.ceilLog2(palette.size())); 70 | int numBits = numBlocks * bitsPerBlock; 71 | int numLongs = (numBits + 63) >>> 6; 72 | long[] data = buf.readLongArray(null, numLongs); 73 | if (data.length != numLongs) { 74 | throw new DecoderException("upload_chunk block data is wrong length"); 75 | } 76 | long mask = (1L << bitsPerBlock) - 1; 77 | 78 | this.blockData = new BlockState[numBlocks]; 79 | for (int blockIndex = 0; blockIndex < numBlocks; blockIndex++) { 80 | int bitIndex = blockIndex * bitsPerBlock; 81 | int longIndex = bitIndex >>> 6; 82 | int endLongIndex = (bitIndex + bitsPerBlock - 1) >>> 6; 83 | int indexInLong = bitIndex & 63; 84 | int value; 85 | if (longIndex == endLongIndex) { 86 | value = (int) ((data[longIndex] >>> indexInLong) & mask); 87 | } else { 88 | int bitsInNextLong = indexInLong + bitsPerBlock - 64; 89 | value = (int) ((data[longIndex] >>> indexInLong) | ((data[endLongIndex] & ((1L << bitsInNextLong) - 1)) << (bitsPerBlock - bitsInNextLong))); 90 | } 91 | if (value >= palette.size()) { 92 | throw new DecoderException("upload_chunk block data contained block not in palette"); 93 | } 94 | this.blockData[blockIndex] = palette.get(value); 95 | } 96 | 97 | this.blockEntities = new NbtCompound[buf.readVarInt()]; 98 | for (int i = 0; i < this.blockEntities.length; i++) { 99 | this.blockEntities[i] = buf.readNbt(); 100 | } 101 | 102 | this.entities = new NbtCompound[buf.readVarInt()]; 103 | for (int i = 0; i < this.entities.length; i++) { 104 | this.entities[i] = buf.readNbt(); 105 | } 106 | 107 | this.entityMirror1 = buf.readEnumConstant(BlockMirror.class); 108 | this.entityMirror2 = buf.readEnumConstant(BlockMirror.class); 109 | this.entityRotation = buf.readEnumConstant(BlockRotation.class); 110 | 111 | this.scheduledBlockTicks = readOrderedTickList(Registries.BLOCK, buf); 112 | this.scheduledFluidTicks = readOrderedTickList(Registries.FLUID, buf); 113 | } 114 | 115 | @Override 116 | public void write(PacketByteBuf buf) { 117 | buf.writeEnumConstant(replaceBehavior); 118 | buf.writeBlockPos(minPos); 119 | buf.writeBlockPos(maxPos); 120 | 121 | List palette = new ArrayList<>(); 122 | Object2IntMap paletteIndexes = new Object2IntOpenCustomHashMap<>(Util.identityHashStrategy()); 123 | for (BlockState state : blockData) { 124 | paletteIndexes.computeIfAbsent(state, k -> { 125 | int index = palette.size(); 126 | palette.add(state); 127 | return index; 128 | }); 129 | } 130 | 131 | buf.writeCollection(palette, UploadChunkPacket::writeBlockState); 132 | int bitsPerBlock = Math.max(1, MathHelper.ceilLog2(palette.size())); 133 | int numBits = blockData.length * bitsPerBlock; 134 | long[] data = new long[(numBits + 63) >>> 6]; 135 | for (int blockIndex = 0; blockIndex < blockData.length; blockIndex++) { 136 | int bitIndex = blockIndex * bitsPerBlock; 137 | int longIndex = bitIndex >>> 6; 138 | int endLongIndex = (bitIndex + bitsPerBlock - 1) >>> 6; 139 | int indexInLong = bitIndex & 63; 140 | int value = paletteIndexes.getInt(blockData[blockIndex]); 141 | data[longIndex] |= (long) value << indexInLong; 142 | if (longIndex != endLongIndex) { 143 | int bitsAlreadyWritten = 64 - indexInLong; 144 | data[endLongIndex] = (long) value >>> bitsAlreadyWritten; 145 | } 146 | } 147 | buf.writeLongArray(data); 148 | 149 | buf.writeVarInt(blockEntities.length); 150 | for (NbtCompound blockEntity : blockEntities) { 151 | buf.writeNbt(blockEntity); 152 | } 153 | 154 | buf.writeVarInt(entities.length); 155 | for (NbtCompound entity : entities) { 156 | buf.writeNbt(entity); 157 | } 158 | 159 | buf.writeEnumConstant(entityMirror1); 160 | buf.writeEnumConstant(entityMirror2); 161 | buf.writeEnumConstant(entityRotation); 162 | 163 | buf.writeCollection(scheduledBlockTicks, (buf1, orderedTick) -> writeOrderedTick(Registries.BLOCK, buf1, orderedTick)); 164 | buf.writeCollection(scheduledFluidTicks, (buf1, orderedTick) -> writeOrderedTick(Registries.FLUID, buf1, orderedTick)); 165 | } 166 | 167 | @SuppressWarnings("unchecked") 168 | private static > BlockState readBlockState(PacketByteBuf buf) { 169 | Identifier blockId = buf.readIdentifier(); 170 | Block block = Registries.BLOCK.get(blockId); 171 | BlockState state = block.getDefaultState(); 172 | int numProperties = buf.readVarInt(); 173 | for (int i = 0; i < numProperties; i++) { 174 | String propertyName = buf.readString(256); 175 | Property property = (Property) block.getStateManager().getProperty(propertyName); 176 | if (property == null) { 177 | continue; 178 | } 179 | String propertyValue = buf.readString(256); 180 | Optional value = property.parse(propertyValue); 181 | if (value.isEmpty()) { 182 | continue; 183 | } 184 | state = state.with(property, value.get()); 185 | } 186 | return state; 187 | } 188 | 189 | @SuppressWarnings("unchecked") 190 | private static > void writeBlockState(PacketByteBuf buf, BlockState state) { 191 | Identifier blockId = Registries.BLOCK.getId(state.getBlock()); 192 | buf.writeIdentifier(blockId); 193 | ImmutableMap, Comparable> entries = state.getEntries(); 194 | buf.writeVarInt(entries.size()); 195 | entries.forEach((p, v) -> { 196 | Property property = (Property) p; 197 | T value = (T) v; 198 | buf.writeString(property.getName(), 256); 199 | buf.writeString(property.name(value), 256); 200 | }); 201 | } 202 | 203 | private static ImmutableList> readOrderedTickList(Registry registry, PacketByteBuf buf) { 204 | int size = buf.readVarInt(); 205 | ImmutableList.Builder> builder = ImmutableList.builderWithExpectedSize(size); 206 | for (int i = 0; i < size; i++) { 207 | builder.add(readOrderedTick(registry, buf)); 208 | } 209 | return builder.build(); 210 | } 211 | 212 | private static OrderedTick readOrderedTick(Registry registry, PacketByteBuf buf) { 213 | Identifier id = buf.readIdentifier(); 214 | T value = registry.get(id); 215 | BlockPos pos = buf.readBlockPos(); 216 | long triggerTick = buf.readVarLong(); 217 | TickPriority priority = TickPriority.byIndex(buf.readUnsignedByte() - 3); 218 | long subTickOrder = buf.readVarLong(); 219 | return new OrderedTick<>(value, pos, triggerTick, priority, subTickOrder); 220 | } 221 | 222 | private static void writeOrderedTick(Registry registry, PacketByteBuf buf, OrderedTick orderedTick) { 223 | buf.writeIdentifier(registry.getId(orderedTick.type())); 224 | buf.writeBlockPos(orderedTick.pos()); 225 | buf.writeVarLong(orderedTick.triggerTick()); 226 | buf.writeByte(orderedTick.priority().getIndex() + 3); 227 | buf.writeVarLong(orderedTick.subTickOrder()); 228 | } 229 | 230 | @Override 231 | public PacketSplitter.SplitPacketType getType() { 232 | return TYPE; 233 | } 234 | 235 | public enum ReplaceBehavior { 236 | NONE, ALL, WITH_NON_AIR 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/litemoretica/server/CarpetCompat.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.litemoretica.server; 2 | 3 | import carpet.CarpetSettings; 4 | import net.fabricmc.loader.api.FabricLoader; 5 | 6 | public final class CarpetCompat { 7 | private static final boolean HAS_CARPET = FabricLoader.getInstance().isModLoaded("carpet"); 8 | 9 | private CarpetCompat() { 10 | } 11 | 12 | public static void onFillUpdatesSkipStart() { 13 | if (HAS_CARPET) { 14 | CarpetSettings.impendingFillSkipUpdates.set(!CarpetSettings.fillUpdates); 15 | } 16 | } 17 | 18 | public static void onFillUpdatesSkipEnd() { 19 | if (HAS_CARPET) { 20 | CarpetSettings.impendingFillSkipUpdates.set(false); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/litemoretica/server/EasyPlaceProtocolServer.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.litemoretica.server; 2 | 3 | import com.google.common.collect.ImmutableSet; 4 | import com.mojang.logging.LogUtils; 5 | import io.netty.buffer.Unpooled; 6 | import net.earthcomputer.litemoretica.network.InitEasyPlaceProtocolPacket; 7 | import net.earthcomputer.litemoretica.network.SetEasyPlaceProtocolPacket; 8 | import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; 9 | import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; 10 | import net.minecraft.block.BedBlock; 11 | import net.minecraft.block.BlockState; 12 | import net.minecraft.block.enums.SlabType; 13 | import net.minecraft.item.ItemPlacementContext; 14 | import net.minecraft.network.PacketByteBuf; 15 | import net.minecraft.server.network.ServerPlayerEntity; 16 | import net.minecraft.state.property.DirectionProperty; 17 | import net.minecraft.state.property.Properties; 18 | import net.minecraft.state.property.Property; 19 | import net.minecraft.util.math.BlockPos; 20 | import net.minecraft.util.math.Direction; 21 | import net.minecraft.util.math.MathHelper; 22 | import org.jetbrains.annotations.Nullable; 23 | import org.slf4j.Logger; 24 | 25 | import java.util.ArrayList; 26 | import java.util.Comparator; 27 | import java.util.List; 28 | 29 | public final class EasyPlaceProtocolServer { 30 | private static final Logger LOGGER = LogUtils.getLogger(); 31 | 32 | // See PlacementHandler.WHITELISTED_PROPERTIES 33 | private static final ImmutableSet> WHITELISTED_PROPERTIES = ImmutableSet.of( 34 | // BooleanProperty: 35 | // INVERTED - DaylightDetector 36 | // OPEN - Barrel, Door, FenceGate, Trapdoor 37 | // PERSISTENT - Leaves 38 | Properties.INVERTED, 39 | Properties.OPEN, 40 | Properties.PERSISTENT, 41 | // EnumProperty: 42 | // AXIS - Pillar 43 | // BLOCK_HALF - Stairs, Trapdoor 44 | // CHEST_TYPE - Chest 45 | // COMPARATOR_MODE - Comparator 46 | // DOOR_HINGE - Door 47 | // SLAB_TYPE - Slab - PARTIAL ONLY: TOP and BOTTOM, not DOUBLE 48 | // STAIR_SHAPE - Stairs (needed to get the correct state, otherwise the player facing would be a factor) 49 | // BLOCK_FACE - Button, Grindstone, Lever 50 | Properties.AXIS, 51 | Properties.BLOCK_HALF, 52 | Properties.CHEST_TYPE, 53 | Properties.COMPARATOR_MODE, 54 | Properties.DOOR_HINGE, 55 | Properties.SLAB_TYPE, 56 | Properties.STAIR_SHAPE, 57 | Properties.BLOCK_FACE, 58 | // IntProperty: 59 | // BITES - Cake 60 | // DELAY - Repeater 61 | // NOTE - NoteBlock 62 | // ROTATION - Banner, Sign, Skull 63 | Properties.BITES, 64 | Properties.DELAY, 65 | Properties.NOTE, 66 | Properties.ROTATION 67 | ); 68 | 69 | private EasyPlaceProtocolServer() { 70 | } 71 | 72 | public static void init() { 73 | ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> { 74 | if (ServerPlayNetworking.canSend(handler, InitEasyPlaceProtocolPacket.TYPE)) { 75 | // TODO: convert these packets to new API in 1.20 76 | PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer()); 77 | new InitEasyPlaceProtocolPacket(WHITELISTED_PROPERTIES).write(buf); 78 | sender.sendPacket(InitEasyPlaceProtocolPacket.TYPE, buf); 79 | } 80 | }); 81 | ServerPlayNetworking.registerGlobalReceiver(SetEasyPlaceProtocolPacket.TYPE, (server, player, handler, buf, responseSender) -> { 82 | SetEasyPlaceProtocolPacket packet = new SetEasyPlaceProtocolPacket(buf); 83 | player.server.execute(() -> { 84 | LOGGER.info("Player {} is using easy place protocol {}", player.getEntityName(), packet.protocol()); 85 | ((NetworkHandlerExt) player.networkHandler).litemoretica_setEasyPlaceProtocol(packet.protocol()); 86 | }); 87 | }); 88 | } 89 | 90 | public static int getEasyPlaceProtocol(ServerPlayerEntity player) { 91 | return ((NetworkHandlerExt) player.networkHandler).litemoretica_getEasyPlaceProtocol(); 92 | } 93 | 94 | public static > BlockState applyEasyPlaceProtocolV3(BlockState state, ItemPlacementContext context) { 95 | int protocolValue = (int) (context.getHitPos().x - (double) context.getBlockPos().getX()) - 2; 96 | //System.out.printf("raw protocol value in: 0x%08X\n", protocolValue); 97 | 98 | if (protocolValue < 0) { 99 | return state; 100 | } 101 | 102 | @Nullable DirectionProperty property = getFirstDirectionProperty(state); 103 | 104 | // DirectionProperty - allow all except: VERTICAL_DIRECTION (PointedDripstone) 105 | if (property != null && property != Properties.VERTICAL_DIRECTION) { 106 | //System.out.printf("applying: 0x%08X\n", protocolValue); 107 | state = applyDirectionProperty(state, context, property, protocolValue); 108 | 109 | if (state == null) { 110 | return null; 111 | } 112 | 113 | // Consume the bits used for the facing 114 | protocolValue >>>= 3; 115 | } 116 | 117 | // Consume the lowest unused bit 118 | protocolValue >>>= 1; 119 | 120 | List> propList = new ArrayList<>(state.getBlock().getStateManager().getProperties()); 121 | propList.sort(Comparator.comparing(Property::getName)); 122 | 123 | try { 124 | for (Property p : propList) { 125 | if (!(p instanceof DirectionProperty) && WHITELISTED_PROPERTIES.contains(p)) { 126 | @SuppressWarnings("unchecked") 127 | Property prop = (Property) p; 128 | List list = new ArrayList<>(prop.getValues()); 129 | list.sort(Comparable::compareTo); 130 | 131 | int requiredBits = MathHelper.floorLog2(MathHelper.smallestEncompassingPowerOfTwo(list.size())); 132 | int bitMask = ~(0xFFFFFFFF << requiredBits); 133 | int valueIndex = protocolValue & bitMask; 134 | //System.out.printf("trying to apply valInd: %d, bits: %d, prot val: 0x%08X\n", valueIndex, requiredBits, protocolValue); 135 | 136 | if (valueIndex < list.size()) { 137 | T value = list.get(valueIndex); 138 | 139 | if (!state.get(prop).equals(value) && allowPropertyValueThroughProtocol(value)) { 140 | //System.out.printf("applying %s: %s\n", prop.getName(), value); 141 | state = state.with(prop, value); 142 | } 143 | 144 | protocolValue >>>= requiredBits; 145 | } 146 | } 147 | } 148 | } catch (Exception e) { 149 | LOGGER.warn("Exception trying to apply placement protocol value", e); 150 | } 151 | 152 | return state; 153 | } 154 | 155 | private static BlockState applyDirectionProperty(BlockState state, ItemPlacementContext context, 156 | DirectionProperty property, int protocolValue) { 157 | Direction facingOrig = state.get(property); 158 | Direction facing = facingOrig; 159 | int decodedFacingIndex = (protocolValue & 0xF) >> 1; 160 | 161 | if (decodedFacingIndex == 6) { 162 | // the opposite of the normal facing requested 163 | facing = facing.getOpposite(); 164 | } else if (decodedFacingIndex <= 5) { 165 | facing = Direction.byId(decodedFacingIndex); 166 | 167 | if (!property.getValues().contains(facing)) { 168 | facing = context.getHorizontalPlayerFacing().getOpposite(); 169 | } 170 | } 171 | 172 | //System.out.printf("plop facing: %s -> %s (raw: %d, dec: %d)\n", facingOrig, facing, rawFacingIndex, decodedFacingIndex); 173 | 174 | if (facing != facingOrig && property.getValues().contains(facing)) { 175 | if (state.getBlock() instanceof BedBlock) { 176 | BlockPos headPos = context.getBlockPos().offset(facing); 177 | 178 | if (!context.getWorld().getBlockState(headPos).canReplace(context)) { 179 | return null; 180 | } 181 | } 182 | 183 | state = state.with(property, facing); 184 | } 185 | 186 | return state; 187 | } 188 | 189 | @Nullable 190 | public static DirectionProperty getFirstDirectionProperty(BlockState state) { 191 | for (Property prop : state.getProperties()) { 192 | if (prop instanceof DirectionProperty) { 193 | return (DirectionProperty) prop; 194 | } 195 | } 196 | 197 | return null; 198 | } 199 | 200 | private static boolean allowPropertyValueThroughProtocol(Comparable value) { 201 | // don't allow duping slabs by forcing a double slab via the protocol 202 | return value != SlabType.DOUBLE; 203 | } 204 | 205 | public interface NetworkHandlerExt { 206 | int litemoretica_getEasyPlaceProtocol(); 207 | void litemoretica_setEasyPlaceProtocol(int easyPlaceProtocol); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/litemoretica/server/LitemoreticaServer.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.litemoretica.server; 2 | 3 | import net.fabricmc.api.ModInitializer; 4 | import net.fabricmc.loader.api.FabricLoader; 5 | 6 | public class LitemoreticaServer implements ModInitializer { 7 | @Override 8 | public void onInitialize() { 9 | if (FabricLoader.getInstance().isModLoaded("fabric-networking-api-v1")) { 10 | EasyPlaceProtocolServer.init(); 11 | PasteHandlerServer.init(); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/net/earthcomputer/litemoretica/server/PasteHandlerServer.java: -------------------------------------------------------------------------------- 1 | package net.earthcomputer.litemoretica.server; 2 | 3 | import net.earthcomputer.litemoretica.network.PacketSplitter; 4 | import net.earthcomputer.litemoretica.network.UploadChunkPacket; 5 | import net.minecraft.block.Block; 6 | import net.minecraft.block.BlockState; 7 | import net.minecraft.block.Blocks; 8 | import net.minecraft.block.entity.BlockEntity; 9 | import net.minecraft.entity.Entity; 10 | import net.minecraft.entity.EntityType; 11 | import net.minecraft.entity.LivingEntity; 12 | import net.minecraft.fluid.Fluid; 13 | import net.minecraft.nbt.NbtCompound; 14 | import net.minecraft.server.network.ServerPlayNetworkHandler; 15 | import net.minecraft.server.world.ServerWorld; 16 | import net.minecraft.util.BlockMirror; 17 | import net.minecraft.util.BlockRotation; 18 | import net.minecraft.util.math.BlockBox; 19 | import net.minecraft.util.math.BlockPos; 20 | import net.minecraft.util.math.Box; 21 | import net.minecraft.world.tick.OrderedTick; 22 | 23 | public final class PasteHandlerServer { 24 | private PasteHandlerServer() { 25 | } 26 | 27 | public static void init() { 28 | PacketSplitter.registerC2S(UploadChunkPacket.TYPE, (packet, handler) -> { 29 | handler.player.server.execute(() -> { 30 | CarpetCompat.onFillUpdatesSkipStart(); 31 | try { 32 | handleChunkUpload(packet, handler); 33 | } finally { 34 | CarpetCompat.onFillUpdatesSkipEnd(); 35 | } 36 | }); 37 | }); 38 | } 39 | 40 | private static void handleChunkUpload(UploadChunkPacket packet, ServerPlayNetworkHandler handler) { 41 | if (!handler.player.isCreative()) { 42 | return; 43 | } 44 | 45 | int chunkX = packet.minPos.getX() >> 4; 46 | int chunkZ = packet.minPos.getZ() >> 4; 47 | if ((packet.maxPos.getX() >> 4) != chunkX || (packet.maxPos.getZ() >> 4) != chunkZ) { 48 | return; 49 | } 50 | 51 | ServerWorld world = handler.player.getServerWorld(); 52 | 53 | BlockBox box = BlockBox.create(packet.minPos, packet.maxPos); 54 | 55 | // load blocks 56 | int blockIndex = 0; 57 | BlockPos.Mutable pos = new BlockPos.Mutable(); 58 | for (int y = box.getMinY(); y <= box.getMaxY(); y++) { 59 | for (int z = box.getMinZ(); z <= box.getMaxZ(); z++) { 60 | for (int x = box.getMinX(); x <= box.getMaxX(); x++) { 61 | BlockState blockToPlace = packet.blockData[blockIndex++]; 62 | if (blockToPlace.isOf(Blocks.STRUCTURE_VOID)) { 63 | continue; 64 | } 65 | BlockState oldState = world.getBlockState(pos.set(x, y, z)); 66 | if ((packet.replaceBehavior == UploadChunkPacket.ReplaceBehavior.NONE && !oldState.isAir()) || (packet.replaceBehavior == UploadChunkPacket.ReplaceBehavior.WITH_NON_AIR && blockToPlace.isAir())) { 67 | continue; 68 | } 69 | world.setBlockState(pos, blockToPlace, Block.FORCE_STATE | Block.NOTIFY_LISTENERS); 70 | } 71 | } 72 | } 73 | 74 | // load block entities 75 | for (NbtCompound blockEntity : packet.blockEntities) { 76 | int x = blockEntity.getInt("x"); 77 | int y = blockEntity.getInt("y"); 78 | int z = blockEntity.getInt("z"); 79 | blockEntity.remove("id"); 80 | 81 | if (!box.contains(x, y, z)) { 82 | continue; 83 | } 84 | 85 | BlockEntity be = world.getBlockEntity(pos.set(x, y, z)); 86 | if (be != null) { 87 | be.readNbt(blockEntity); 88 | } 89 | } 90 | 91 | // load entities (and remove old ones) 92 | world.getOtherEntities(null, Box.from(box), entity -> { 93 | return box.contains(entity.getBlockPos()); 94 | }).forEach(entity -> entity.remove(Entity.RemovalReason.DISCARDED)); 95 | for (NbtCompound entity : packet.entities) { 96 | entity.remove(Entity.UUID_KEY); 97 | Entity loadedEntity = EntityType.loadEntityWithPassengers(entity, world, ent -> { 98 | float yaw = ent.getYaw(); 99 | if (packet.entityMirror1 != BlockMirror.NONE) { 100 | yaw = ent.applyMirror(packet.entityMirror1); 101 | } 102 | if (packet.entityMirror2 != BlockMirror.NONE) { 103 | yaw = ent.applyMirror(packet.entityMirror2); 104 | } 105 | if (packet.entityRotation != BlockRotation.NONE) { 106 | yaw = ent.applyRotation(packet.entityRotation); 107 | } 108 | ent.refreshPositionAndAngles(ent.getBlockPos(), yaw, ent.getPitch()); 109 | if (ent instanceof LivingEntity living) { 110 | living.headYaw = yaw; 111 | living.bodyYaw = yaw; 112 | living.prevHeadYaw = yaw; 113 | living.prevBodyYaw = yaw; 114 | } 115 | return ent; 116 | }); 117 | if (loadedEntity != null) { 118 | if (box.contains(loadedEntity.getBlockPos())) { 119 | world.spawnNewEntityAndPassengers(loadedEntity); 120 | } 121 | } 122 | } 123 | 124 | // load scheduled ticks 125 | world.getBlockTickScheduler().clearNextTicks(box); 126 | for (OrderedTick blockTick : packet.scheduledBlockTicks) { 127 | if (box.contains(blockTick.pos())) { 128 | world.getBlockTickScheduler().scheduleTick(blockTick); 129 | } 130 | } 131 | world.getFluidTickScheduler().clearNextTicks(box); 132 | for (OrderedTick fluidTick : packet.scheduledFluidTicks) { 133 | if (box.contains(fluidTick.pos())) { 134 | world.getFluidTickScheduler().scheduleTick(fluidTick); 135 | } 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/main/resources/assets/litemoretica/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Earthcomputer/litemoretica/817a384da157aeb7ff0a051a4d8b8711f04f5ee3/src/main/resources/assets/litemoretica/icon.png -------------------------------------------------------------------------------- /src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "litemoretica", 4 | "version": "${version}", 5 | "name": "litemoretica", 6 | "description": "Adds extra features to litematica", 7 | "authors": [ 8 | "Earthcomputer" 9 | ], 10 | "contact": { 11 | "repo": "https://github.com/Earthcomputer/litemoretica" 12 | }, 13 | "license": "MIT", 14 | "icon": "assets/litemoretica/icon.png", 15 | "environment": "*", 16 | "entrypoints": { 17 | "client": [ 18 | "net.earthcomputer.litemoretica.client.LitemoreticaClient" 19 | ], 20 | "main": [ 21 | "net.earthcomputer.litemoretica.server.LitemoreticaServer" 22 | ] 23 | }, 24 | "mixins": [ 25 | "litemoretica.client.mixins.json", 26 | "litemoretica.server.mixins.json" 27 | ], 28 | "depends": { 29 | "minecraft": "${mcversions}", 30 | "fabricloader": ">=0.15.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/resources/litemoretica.client.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "net.earthcomputer.litemoretica.mixin.client", 5 | "compatibilityLevel": "JAVA_17", 6 | "mixins": [ 7 | "ConfigsGenericMixin", 8 | "HotkeysMixin", 9 | "PlacementHandlerAccessor", 10 | "PlacementHandlerMixin_EasyPlaceProtocol", 11 | "RayTraceUtilsMixin_ShapeContextFix", 12 | "SchematicPlacementManagerMixin_PasteHandler", 13 | "SchematicUtilsAccessor", 14 | "TaskCountBlocksPlacementMixin_MaterialListFeature", 15 | "WorldUtilsAccessor", 16 | "WorldUtilsMixin_EasyPlaceFix" 17 | ], 18 | "client": [ 19 | "ClientPlayerInteractionManagerMixin_EasyPlaceFix", 20 | "ClientPlayNetworkHandlerMixin_PacketSplitter" 21 | ], 22 | "injectors": { 23 | "defaultRequire": 1 24 | }, 25 | "overwrites": { 26 | "requireAnnotations": true 27 | }, 28 | "plugin": "net.earthcomputer.litemoretica.mixin.LitemoreticaMixinPlugin" 29 | } 30 | -------------------------------------------------------------------------------- /src/main/resources/litemoretica.server.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "net.earthcomputer.litemoretica.mixin.server", 5 | "compatibilityLevel": "JAVA_17", 6 | "mixins": [ 7 | "BlockItemMixin_EasyPlaceProtocol", 8 | "ServerPlayNetworkHandlerMixin_EasyPlaceProtocol", 9 | "ServerPlayNetworkHandlerMixin_PacketSplitter" 10 | ], 11 | "client": [ 12 | ], 13 | "injectors": { 14 | "defaultRequire": 1 15 | }, 16 | "overwrites": { 17 | "requireAnnotations": true 18 | }, 19 | "plugin": "net.earthcomputer.litemoretica.mixin.LitemoreticaMixinPlugin" 20 | } 21 | --------------------------------------------------------------------------------