diff --git a/build.gradle b/build.gradle index c2cb2d0..cf53c88 100644 --- a/build.gradle +++ b/build.gradle @@ -2,28 +2,27 @@ import com.modrinth.minotaur.TaskModrinthUpload plugins { id "java-library" - id "fabric-loom" version "0.5-SNAPSHOT" + id "fabric-loom" version "0.8-SNAPSHOT" id "com.matthewprenger.cursegradle" version "1.4.0" - id "com.modrinth.minotaur" version "1.1.0" + id "com.modrinth.minotaur" version "1.2.1" } ext { commonGradleBranch = "master" - minecraftVersion = "1.16.5" - yarnMappings = "1.16.5+build.1" - loaderVersion = "0.11.1" - fabricAPIVersion = "0.29.3+1.16" - modMenuVersion = "1.14.13+build.19" - clothConfigVersion = "4.8.3" - autoConfigVersion = "3.3.1" - autoConfigTOMLVersion = "autoconfig-3.x.x-fabric-SNAPSHOT" + minecraftVersion = "1.17" + yarnMappings = "1.17+build.10" + loaderVersion = "0.11.6" + fabricAPIVersion = "0.35.2+1.17" + modMenuVersion = "2.0.2" + clothConfigVersion = "5.0.34" } -version = "2.0.6-fabric" +version = "2.1.0-fabric" group = "com.therandomlabs.vanilladeathchest" archivesBaseName = "vanilladeathchest" -apply from: "https://raw.githubusercontent.com/TheRandomLabs/Common-Gradle/${project.commonGradleBranch}/fabric.gradle" +//apply from: "https://raw.githubusercontent.com/TheRandomLabs/Common-Gradle/${project.commonGradleBranch}/fabric.gradle" +apply from: "fabric.gradle" if (project.hasProperty("curseForgeAPIKey")) { curseforge { @@ -33,9 +32,11 @@ if (project.hasProperty("curseForgeAPIKey")) { id = "393000" addGameVersion "Fabric" + addGameVersion "Java 16" addGameVersion "Java 10" addGameVersion "Java 9" addGameVersion "Java 8" + addGameVersion "1.17" addGameVersion "1.16.5" addGameVersion "1.16.4" addGameVersion "1.16.3" @@ -83,6 +84,7 @@ if (project.hasProperty("modrinthToken")) { changelog = new File("changelog.md").getText() uploadFile = remapJar releaseType = "alpha" + addGameVersion("1.17") addGameVersion("1.16.5") addGameVersion("1.16.4") addGameVersion("1.16.3") diff --git a/fabric.gradle b/fabric.gradle new file mode 100644 index 0000000..989b67a --- /dev/null +++ b/fabric.gradle @@ -0,0 +1,139 @@ +def branch = project.findProperty("commonGradleBranch") ?: "master" +def includeClothConfig = !project.hasProperty("includeClothConfig") || project.includeClothConfig +def testMod = project.findProperty("testMod") + +ext { + useSpotBugs = false + testing = false + defaultCompileDependencies = false + includeLicense = false + buildSourcesJar = false + registerDefaultMavenPublication = false +} + +apply from: "https://raw.githubusercontent.com/TheRandomLabs/Common-Gradle/$branch/build.gradle" + +if (testMod) { + sourceSets { + testmod { + compileClasspath += main.compileClasspath + runtimeClasspath += main.runtimeClasspath + } + } +} + +repositories { + if (project.hasProperty("clothConfigVersion")) { + maven { url "https://maven.shedaniel.me/" } + } + + if (project.hasProperty("autoConfigVersion")) { + maven { + url "https://dl.bintray.com/shedaniel/autoconfig1u" + } + } + + if (project.hasProperty("autoConfigTOMLVersion")) { + maven { + url "https://jitpack.io" + } + } + + if (project.hasProperty("modMenuVersion")) { + maven { + url "https://maven.terraformersmc.com/releases/" + } + } +} + +dependencies { + minecraft "com.mojang:minecraft:${project.minecraftVersion}" + mappings "net.fabricmc:yarn:${project.yarnMappings}:v2" + modImplementation "net.fabricmc:fabric-loader:${project.loaderVersion}" + + if (project.hasProperty("fabricAPIVersion")) { + modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabricAPIVersion}" + } + + if (project.hasProperty("modMenuVersion")) { + modImplementation "com.terraformersmc:modmenu:${modMenuVersion}" + } + + if (project.hasProperty("clothConfigVersion")) { + modApi("me.shedaniel.cloth:cloth-config-fabric:${project.clothConfigVersion}") { + exclude(group: "net.fabricmc.fabric-api") + } + + if (includeClothConfig) { + include "me.shedaniel.cloth:cloth-config-fabric:${project.clothConfigVersion}" + } + } + + if (project.hasProperty("autoConfigVersion")) { + modImplementation("me.sargunvohra.mcmods:autoconfig1u:${project.autoConfigVersion}") { + exclude(module: "fabric-api") + } + + if (includeClothConfig) { + include "me.sargunvohra.mcmods:autoconfig1u:${project.autoConfigVersion}" + } + } + + if (project.hasProperty("autoConfigTOMLVersion")) { + + modImplementation "com.github.TheRandomLabs:AutoConfig-TOML:${project.autoConfigTOMLVersion}" + include "com.github.TheRandomLabs:AutoConfig-TOML:${project.autoConfigTOMLVersion}" + } + + if (testMod) { + afterEvaluate { + testmodImplementation(sourceSets.main.output) + } + } +} + +processResources { + inputs.property "version", project.version + + filesMatching("fabric.mod.json") { + expand "version": project.version + } +} + +java { + withSourcesJar() +} + +jar { + from("LICENSE") { + rename { + "${it}_${project.archivesBaseName}" + } + } +} + +if (testMod) { + runClient { + classpath(sourceSets.testmod.runtimeClasspath) + } + + runServer { + classpath(sourceSets.testmod.runtimeClasspath) + } +} + +publishing { + publications { + mavenJava(MavenPublication) { + artifact(remapJar) { + builtBy remapJar + } + + artifact(sourcesJar) { + builtBy remapSourcesJar + } + + artifact javadocJar + } + } +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4d9ca16..0f80bbf 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew.bat b/gradlew.bat index 107acd3..ac1b06f 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,89 +1,89 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/main/java/com/therandomlabs/vanilladeathchest/VDCConfig.java b/src/main/java/com/therandomlabs/vanilladeathchest/VDCConfig.java index 3298550..13beea2 100644 --- a/src/main/java/com/therandomlabs/vanilladeathchest/VDCConfig.java +++ b/src/main/java/com/therandomlabs/vanilladeathchest/VDCConfig.java @@ -30,13 +30,11 @@ import java.util.Set; import java.util.stream.Collectors; -import com.electronwill.nightconfig.core.conversion.SpecDoubleInRange; -import com.electronwill.nightconfig.core.conversion.SpecIntInRange; import com.mojang.brigadier.exceptions.CommandSyntaxException; -import com.therandomlabs.autoconfigtoml.TOMLConfigSerializer; -import me.sargunvohra.mcmods.autoconfig1u.ConfigData; -import me.sargunvohra.mcmods.autoconfig1u.annotation.Config; -import me.sargunvohra.mcmods.autoconfig1u.annotation.ConfigEntry; +import me.shedaniel.autoconfig.ConfigData; +import me.shedaniel.autoconfig.annotation.Config; +import me.shedaniel.autoconfig.annotation.ConfigEntry; +import me.shedaniel.cloth.clothconfig.shadowed.blue.endless.jankson.Comment; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityType; import net.minecraft.item.Item; @@ -49,104 +47,96 @@ import org.checkerframework.checker.nullness.qual.Nullable; @SuppressWarnings("CanBeFinal") -@TOMLConfigSerializer.Comment("VanillaDeathChest configuration.") +//@Comment("VanillaDeathChest configuration.") @Config(name = VanillaDeathChest.MOD_ID) public final class VDCConfig implements ConfigData { public static final class Spawning implements ConfigData { - @TOMLConfigSerializer.Comment({ - "The death chest container type.", - "SINGLE_CHEST: Only single chests.", - "SINGLE_OR_DOUBLE_CHEST: Single or double chests.", - "SINGLE_SHULKER_BOX: Single shulker boxes.", - "SINGLE_OR_DOUBLE_SHULKER_BOX: Single or double shulker boxes." - }) - @ConfigEntry.Gui.Tooltip + @Comment( + "The death chest container type.\n" + + "SINGLE_CHEST: Only single chests.\n" + + "SINGLE_OR_DOUBLE_CHEST: Single or double chests.\n" + + "SINGLE_SHULKER_BOX: Single shulker boxes.\n" + + "SINGLE_OR_DOUBLE_SHULKER_BOX: Single or double shulker boxes." + ) + @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) public ContainerType containerType = ContainerType.SINGLE_OR_DOUBLE_CHEST; - @TOMLConfigSerializer.Comment({ - "The color of the shulker box if the container type is a shulker box.", - "WHITE: White.", - "ORANGE: Orange.", - "MAGENTA: Magenta.", - "LIGHT_BLUE: Light blue.", - "YELLOW: Yellow.", - "LIME: Lime.", - "PINK: Pink.", - "GRAY: Gray.", - "LIGHT_GRAY: Light gray.", - "CYAN: Cyan.", - "PURPLE: Purple.", - "BLUE: Blue.", - "BROWN: Brown.", - "GREEN: Green.", - "RED: Red.", - "BLACK: Black.", - "RANDOM: Random color." - }) - @ConfigEntry.Gui.Tooltip + @Comment( + "The color of the shulker box if the container type is a shulker box.\n" + + "WHITE: White.\n" + + "ORANGE: Orange.\n" + + "MAGENTA: Magenta.\n" + + "LIGHT_BLUE: Light blue.\n" + + "YELLOW: Yellow.\n" + + "LIME: Lime.\n" + + "PINK: Pink.\n" + + "GRAY: Gray.\n" + + "LIGHT_GRAY: Light gray.\n" + + "CYAN: Cyan.\n" + + "PURPLE: Purple.\n" + + "BLUE: Blue.\n" + + "BROWN: Brown.\n" + + "GREEN: Green.\n" + + "RED: Red.\n" + + "BLACK: Black.\n" + + "RANDOM: Random color." + ) + @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) public ShulkerBoxColor shulkerBoxColor = ShulkerBoxColor.WHITE; - @TOMLConfigSerializer.Comment( + @Comment( "The dimensions that death chests should or should not spawn in." ) - @ConfigEntry.Gui.Tooltip + @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) public List dimensions = new ArrayList<>(); - @TOMLConfigSerializer.Comment({ - "Whether the dimensions list should be a blacklist or a whitelist.", - "BLACKLIST: blacklist.", + @Comment("Whether the dimensions list should be a blacklist or a whitelist.\n" + + "BLACKLIST: blacklist\n" + "WHITELIST: whitelist." - }) - @ConfigEntry.Gui.Tooltip + ) + @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) public DimensionListBehavior dimensionsBehavior = DimensionListBehavior.BLACKLIST; - @SpecIntInRange(min = 1, max = Integer.MAX_VALUE) - @TOMLConfigSerializer.Comment( + @ConfigEntry.BoundedDiscrete(min = 1, max = 1000) + @Comment( "The radius around the location of a player's death in which a suitable death " + "chest placement location should be searched for." ) - @ConfigEntry.Gui.Tooltip public int locationSearchRadius = 8; - @TOMLConfigSerializer.Comment( + @Comment( "Causes a death chest to be forcibly placed at the location of a player's death " + "if no suitable locations are found nearby." ) - @ConfigEntry.Gui.Tooltip public boolean forcePlacementIfNoSuitableLocation = true; - @TOMLConfigSerializer.Comment("Requires death chest placement to be on solid blocks.") - @ConfigEntry.Gui.Tooltip + @Comment("Requires death chest placement to be on solid blocks.") public boolean requirePlacementOnSolidBlocks; - @TOMLConfigSerializer.Comment( + @Comment( "A regular expression that matches the registry names of items that can be " + "placed in death chests." ) - @ConfigEntry.Gui.Tooltip public String registryNameRegex = ".+"; - @TOMLConfigSerializer.Comment({ + @Comment( "Causes death chests to only be spawned if the necessary container is in the " + - "player's inventory.", - "If this is enabled, the container is consumed if it is found." - }) - @ConfigEntry.Gui.Tooltip + "player's inventory.\n" + + "If this is enabled, the container is consumed if it is found." + ) public boolean useContainerInInventory; - @TOMLConfigSerializer.Comment({ - "The display name of the death chest container.", - "Set this to an empty string to cause a custom display name to not be used." - }) - @ConfigEntry.Gui.Tooltip + @Comment( + "The display name of the death chest container.\n" + + "Set this to an empty string to cause a custom display name to not be used." + ) public String containerDisplayName = "Death Chest"; - @TOMLConfigSerializer.Comment({ - "The message sent to a player after a death chest is placed when they die.", - "The X, Y and Z coordinates are provided as arguments.", - "Set this to an empty string to disable this message." - }) - @ConfigEntry.Gui.Tooltip + @Comment( + "The message sent to a player after a death chest is placed when they die.\n" + + "The X, Y and Z coordinates are provided as arguments.\n" + + "Set this to an empty string to disable this message." + ) public String spawnMessage = "Death chest spawned at [%s, %s, %s]"; @Nullable @@ -197,54 +187,50 @@ public boolean isDimensionEnabled(World world) { } public static final class KeyItem implements ConfigData { - @TOMLConfigSerializer.Comment({ - "The registry name of the key item.", - "A player must be holding this item in their main hand to unlock a death chest.", + @Comment( + "The registry name of the key item.\n" + + "A player must be holding this item in their main hand to unlock a death chest.\n" + "Set this to an empty string to allow death chests to be unlocked without an item." - }) - @ConfigEntry.Gui.Tooltip + ) public String registryName = ""; - @SpecIntInRange(min = 0, max = Short.MAX_VALUE) - @TOMLConfigSerializer.Comment({ - "The meta value of the key item.", - "Set this to " + Short.MAX_VALUE + " to not require a specific meta value." - }) - @ConfigEntry.Gui.Tooltip + //@IntRange(from = 0, to= Integer.MAX_VALUE) + //@ConfigEntry.BoundedDiscrete(min = 0, max = Short.MAX_VALUE) + @Comment( + "The meta value of the key item.\n" + + "Set this to " + Short.MAX_VALUE + " to not require a specific meta value." + ) public int meta = Short.MAX_VALUE; - @TOMLConfigSerializer.Comment({ - "The key consumption behavior.", - "CONSUME: Consume the item.", - "DAMAGE: Damage the item." - }) - @ConfigEntry.Gui.Tooltip + @Comment( + "The key consumption behavior.\n" + + "CONSUME: Consume the item.\n" + + "DAMAGE: Damage the item." + ) + @ConfigEntry.Gui.EnumHandler(option = ConfigEntry.Gui.EnumHandler.EnumDisplayOption.BUTTON) public KeyConsumptionBehavior consumptionBehavior = KeyConsumptionBehavior.CONSUME; - @SpecIntInRange(min = 0, max = Short.MAX_VALUE) - @TOMLConfigSerializer.Comment({ - "The amount by which the key item should be consumed.", + @ConfigEntry.BoundedDiscrete(min = 0, max = 100) + @Comment( + "The amount by which the key item should be consumed.\n" + "If the key item cannot be consumed this many times, the death chest will not " + - "be unlocked.", + "be unlocked.\n" + "Players in creative mode will not have their key item consumed." - }) - @ConfigEntry.Gui.Tooltip + ) public int amountToConsume = 1; - @TOMLConfigSerializer.Comment({ - "The message that is sent to the player when they fail to unlock a death chest.", + @Comment( + "The message that is sent to the player when they fail to unlock a death chest.\n" + "This string takes the required amount (%1$s) and display name (%2$s) of the " + - "item as arguments.", + "item as arguments.\n" + "Set this to an empty string to disable this message." - }) - @ConfigEntry.Gui.Tooltip + ) public String unlockFailureMessage = "You need %s of %s to retrieve your items"; - @TOMLConfigSerializer.Comment( + @Comment( "Whether the unlock failed message should be sent as a status message rather " + "than a chat message." ) - @ConfigEntry.Gui.Tooltip public boolean unlockFailureStatusMessage = true; @Nullable @@ -270,53 +256,46 @@ public void validatePostLoad() { } public static final class DefenseEntities implements ConfigData { - @TOMLConfigSerializer.Comment({ - "The registry name of the defense entity.", - "If the defense entity is a living entity, it will not automatically despawn.", - "If the defense entity can have a revenge target, then the revenge target will " + - "be set to its player.", + @Comment( + "The registry name of the defense entity.\n" + + "If the defense entity is a living entity, it will not automatically despawn.\n" + + "If the defense entity can have a revenge target, then the revenge target will\n" + + "be set to its player.\n" + "Set this to an empty string to disable defense entities." - }) - @ConfigEntry.Gui.Tooltip + ) public String registryName = ""; - @TOMLConfigSerializer.Comment("Custom NBT data for defense entities in JSON format.") - @ConfigEntry.Gui.Tooltip + @Comment("Custom NBT data for defense entities in JSON format.") public String nbtTag = "{}"; - @TOMLConfigSerializer.Comment("Causes defense entities to drop experience.") - @ConfigEntry.Gui.Tooltip + @Comment("Causes defense entities to drop experience.") public boolean dropExperience; - @TOMLConfigSerializer.Comment("Causes defense entities to drop items.") - @ConfigEntry.Gui.Tooltip + @Comment("Causes defense entities to drop items.") public boolean dropItems; - @SpecIntInRange(min = 1, max = Integer.MAX_VALUE) - @TOMLConfigSerializer.Comment( + @ConfigEntry.BoundedDiscrete(min = 1, max = 100) + @Comment( "The number of defense entities that are spawned when a death chest is placed." ) - @ConfigEntry.Gui.Tooltip public int spawnCount = 3; - @SpecDoubleInRange(min = 0.0, max = Double.MAX_VALUE) - @TOMLConfigSerializer.Comment({ + //@SpecDoubleInRange(min = 0.0, max = Double.MAX_VALUE) + @Comment( "The maximum squared distance that a defense entity can be from its chest when " + - "a player is not nearby.", + "a player is not nearby.\n" + "Set this to 0.0 to disable the limit so that defense entities are not " + - "teleported back to their death chest." - }) - @ConfigEntry.Gui.Tooltip + "teleported back to their death chest." + ) public double maxSquaredDistanceFromChest = 64.0; - @SpecDoubleInRange(min = 0.0, max = Double.MAX_VALUE) - @TOMLConfigSerializer.Comment({ + //@SpecDoubleInRange(min = 0.0, max = Double.MAX_VALUE) + @Comment( "The maximum squared distance that a defense entity can be from its player when" + - "its chest is not within the maximum squared distance.", + "its chest is not within the maximum squared distance.\n" + "Set this to 0.0 to disable the limit so that defense entities are teleported " + - "back to their death chest regardless of their distance from the player." - }) - @ConfigEntry.Gui.Tooltip + "back to their death chest regardless of their distance from the player." + ) public double maxSquaredDistanceFromPlayer = 64.0; @Nullable @@ -349,66 +328,58 @@ public void validatePostLoad() { } } - public static final class Protection { - @TOMLConfigSerializer.Comment({ - "Enables death chest protection.", - "When a death chest is protected, it can only be unlocked by its owner." - }) - @ConfigEntry.Gui.Tooltip + public static final class Protection implements ConfigData { + @Comment( + "Enables death chest protection.\n" + + "When a death chest is protected, it can only be unlocked by its owner.\n" + ) public boolean enable = true; - @SpecIntInRange(min = 0, max = Integer.MAX_VALUE) - @TOMLConfigSerializer.Comment( + @ConfigEntry.BoundedDiscrete(min = 0, max = 5) + @Comment( "The required permission level to bypass death chest protection." ) - @ConfigEntry.Gui.Tooltip public int bypassPermissionLevel = 3; - @TOMLConfigSerializer.Comment( + @Comment( "Causes players in creative mode to be able to bypass death chest protection." ) - @ConfigEntry.Gui.Tooltip public boolean bypassInCreativeMode = true; - @SpecIntInRange(min = 0, max = Integer.MAX_VALUE) - @TOMLConfigSerializer.Comment({ - "The length of death chest protection in ticks.", - "120000 ticks is five in-game days.", + //@IntRange(from = 0, to= Integer.MAX_VALUE) + //@ConfigEntry.BoundedDiscrete(min = 0, max = Integer.MAX_VALUE) + @Comment( + "The length of death chest protection in ticks.\n" + + "120000 ticks is five in-game days.\n" + "Set this to 0 to cause death chests to be protected indefinitely." - }) - @ConfigEntry.Gui.Tooltip + ) public int period = 120000; } - public static final class Misc { - @TOMLConfigSerializer.Comment("Causes death chests to be removed when they are emptied.") - @ConfigEntry.Gui.Tooltip + public static final class Misc implements ConfigData { + @Comment("Causes death chests to be removed when they are emptied.") public boolean removeEmptyDeathChests = true; - @TOMLConfigSerializer.Comment( + @Comment( "Whether empty death chests should only be removed when they are closed." ) - @ConfigEntry.Gui.Tooltip public boolean onlyRemoveClosedEmptyDeathChests; - @TOMLConfigSerializer.Comment("Causes death chests to be dropped when they are broken.") - @ConfigEntry.Gui.Tooltip + @Comment("Causes death chests to be dropped when they are broken.") public boolean dropDeathChests; - @TOMLConfigSerializer.Comment({ - "The name of the game rule that controls whether death chests should be spawned.", - "Set this to an empty string to disable the game rule.", - "Changes to this option are applied after a game restart." - }) - @ConfigEntry.Gui.Tooltip + @Comment( + "The name of the game rule that controls whether death chests should be spawned.\n" + + "Set this to an empty string to disable the game rule.\n" + + "Changes to this option are applied after a game restart." + ) public String gameRuleName = "spawnDeathChests"; - @TOMLConfigSerializer.Comment({ - "The name of the command that reloads this configuration from disk.", - "Set this to an empty string to disable the command.", - "Changes to this option are applied when a server is loaded." - }) - @ConfigEntry.Gui.Tooltip + @Comment( + "The name of the command that reloads this configuration from disk.\n" + + "Set this to an empty string to disable the command.\n" + + "Changes to this option are applied when a server is loaded." + ) public String configReloadCommand = "vdcconfigreload"; } @@ -554,27 +525,45 @@ public enum KeyConsumptionBehavior { DAMAGE } - @TOMLConfigSerializer.Comment("Options related to death chest spawning.") + /** + * + * {@inheritDoc} + */ + @Override + public void validatePostLoad() { + try { + spawning.validatePostLoad(); + keyItem.validatePostLoad(); + defenseEntities.validatePostLoad(); + protection.validatePostLoad(); + misc.validatePostLoad(); + } catch (ValidationException e) { + throw new RuntimeException(e); + } + } + + //Comments seem to hide the comments within the class + //@Comment("Options related to death chest spawning.") @ConfigEntry.Category("spawning") @ConfigEntry.Gui.TransitiveObject public Spawning spawning = new Spawning(); - @TOMLConfigSerializer.Comment("Options related to the death chest key item.") + //@Comment("Options related to the death chest key item.") @ConfigEntry.Category("key_item") @ConfigEntry.Gui.TransitiveObject public KeyItem keyItem = new KeyItem(); - @TOMLConfigSerializer.Comment("Options related to death chest defense entities.") + //@Comment("Options related to death chest defense entities.") @ConfigEntry.Category("defense_entities") @ConfigEntry.Gui.TransitiveObject public DefenseEntities defenseEntities = new DefenseEntities(); - @TOMLConfigSerializer.Comment("Options related to death chest protection.") + //@Comment("Options related to death chest protection.") @ConfigEntry.Category("protection") @ConfigEntry.Gui.TransitiveObject public Protection protection = new Protection(); - @TOMLConfigSerializer.Comment("Miscellaneous options.") + //@Comment("Miscellaneous options.") @ConfigEntry.Category("misc") @ConfigEntry.Gui.TransitiveObject public Misc misc = new Misc(); diff --git a/src/main/java/com/therandomlabs/vanilladeathchest/VDCModMenuEntryPoint.java b/src/main/java/com/therandomlabs/vanilladeathchest/VDCModMenuEntryPoint.java index de3d3bd..0d816e7 100644 --- a/src/main/java/com/therandomlabs/vanilladeathchest/VDCModMenuEntryPoint.java +++ b/src/main/java/com/therandomlabs/vanilladeathchest/VDCModMenuEntryPoint.java @@ -23,19 +23,20 @@ package com.therandomlabs.vanilladeathchest; -import io.github.prospector.modmenu.api.ConfigScreenFactory; -import io.github.prospector.modmenu.api.ModMenuApi; -import me.sargunvohra.mcmods.autoconfig1u.AutoConfig; +import com.terraformersmc.modmenu.api.ConfigScreenFactory; +import com.terraformersmc.modmenu.api.ModMenuApi; +import me.shedaniel.autoconfig.AutoConfig; /** * The Mod Menu entry point for VanillaDeathChest. */ public final class VDCModMenuEntryPoint implements ModMenuApi { /** + * * {@inheritDoc} */ @Override public ConfigScreenFactory getModConfigScreenFactory() { - return parent -> AutoConfig.getConfigScreen(VDCConfig.class, parent).get(); + return screen -> AutoConfig.getConfigScreen(VDCConfig.class, screen).get(); } } diff --git a/src/main/java/com/therandomlabs/vanilladeathchest/VanillaDeathChest.java b/src/main/java/com/therandomlabs/vanilladeathchest/VanillaDeathChest.java index f73ba74..8900b25 100644 --- a/src/main/java/com/therandomlabs/vanilladeathchest/VanillaDeathChest.java +++ b/src/main/java/com/therandomlabs/vanilladeathchest/VanillaDeathChest.java @@ -23,13 +23,13 @@ package com.therandomlabs.vanilladeathchest; -import com.therandomlabs.autoconfigtoml.TOMLConfigSerializer; import com.therandomlabs.vanilladeathchest.command.VDCCommand; import com.therandomlabs.vanilladeathchest.deathchest.DeathChestAutoRemover; import com.therandomlabs.vanilladeathchest.deathchest.DeathChestInteractions; import com.therandomlabs.vanilladeathchest.deathchest.DeathChestPlacer; import com.therandomlabs.vanilladeathchest.world.DeathChestsState; -import me.sargunvohra.mcmods.autoconfig1u.AutoConfig; +import me.shedaniel.autoconfig.AutoConfig; +import me.shedaniel.autoconfig.serializer.JanksonConfigSerializer; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerBlockEntityEvents; @@ -63,7 +63,8 @@ public final class VanillaDeathChest implements ModInitializer { @SuppressWarnings("PMD.NonThreadSafeSingleton") @Nullable - private static TOMLConfigSerializer serializer; + private static VDCConfig config; + private static boolean didRegister = false; static { final String gameRuleName = VanillaDeathChest.config().misc.gameRuleName; @@ -97,24 +98,23 @@ public void onInitialize() { */ @SuppressWarnings("NullAway") public static VDCConfig config() { - if (serializer == null) { + if (config == null) { reloadConfig(); } - return serializer.getConfig(); + return config; } /** * Reloads the VanillaDeathChest configuration from disk. */ public static void reloadConfig() { - if (serializer == null) { - AutoConfig.register(VDCConfig.class, (definition, configClass) -> { - serializer = new TOMLConfigSerializer<>(definition, configClass); - return serializer; - }); - } else { - serializer.reloadFromDisk(); + if (!didRegister) { + didRegister = true; + AutoConfig.register(VDCConfig.class, JanksonConfigSerializer::new); + //AutoConfig.register(VDCConfig.class, Toml4jConfigSerializer::new); } + + config = AutoConfig.getConfigHolder(VDCConfig.class).getConfig(); } } diff --git a/src/main/java/com/therandomlabs/vanilladeathchest/command/VDCCommand.java b/src/main/java/com/therandomlabs/vanilladeathchest/command/VDCCommand.java index 2bab94d..4c1770c 100644 --- a/src/main/java/com/therandomlabs/vanilladeathchest/command/VDCCommand.java +++ b/src/main/java/com/therandomlabs/vanilladeathchest/command/VDCCommand.java @@ -140,7 +140,7 @@ private static int executeRestoreInventory( Collection players ) throws CommandSyntaxException { for (ServerPlayerEntity player : players) { - final PlayerInventory inventory = player.inventory; + final PlayerInventory inventory = player.getInventory(); for (int i = 0; i < inventory.size(); i++) { inventory.setStack(i, deathChest.getInventory().getStack(i).copy()); diff --git a/src/main/java/com/therandomlabs/vanilladeathchest/deathchest/DeathChest.java b/src/main/java/com/therandomlabs/vanilladeathchest/deathchest/DeathChest.java index b6890f2..fe8c5c3 100644 --- a/src/main/java/com/therandomlabs/vanilladeathchest/deathchest/DeathChest.java +++ b/src/main/java/com/therandomlabs/vanilladeathchest/deathchest/DeathChest.java @@ -37,10 +37,10 @@ import net.minecraft.entity.ItemEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerInventory; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; import net.minecraft.nbt.NbtHelper; -import net.minecraft.nbt.Tag; +import net.minecraft.nbt.NbtList; import net.minecraft.server.OperatorEntry; import net.minecraft.server.world.ServerWorld; import net.minecraft.util.math.BlockPos; @@ -201,7 +201,7 @@ public void setLocked(boolean flag) { public boolean exists() { //We don't bother checking the east block, as it is impossible to only break one //block in a double death chest. - if (!world.getBlockState(pos).getBlock().hasBlockEntity()) { + if (!world.getBlockState(pos).hasBlockEntity()) { return false; } @@ -223,7 +223,7 @@ public boolean isProtectedFrom(PlayerEntity player) { final VDCConfig.Protection config = VanillaDeathChest.config().protection; if (!config.enable || playerUUID.equals(player.getUuid()) || - (config.bypassInCreativeMode && player.abilities.creativeMode)) { + (config.bypassInCreativeMode && player.getAbilities().creativeMode)) { return false; } @@ -239,27 +239,27 @@ public boolean isProtectedFrom(PlayerEntity player) { } /** - * Serializes this death chest to a {@link CompoundTag}. + * Serializes this death chest to a {@link NbtCompound}. * - * @param tag a {@link CompoundTag}. - * @return the {@link CompoundTag}. + * @param tag a {@link NbtCompound}. + * @return the {@link NbtCompound}. */ - public CompoundTag toTag(CompoundTag tag) { + public NbtCompound writeNbt(NbtCompound tag) { tag.put("Identifier", NbtHelper.fromUuid(identifier)); tag.put("PlayerUUID", NbtHelper.fromUuid(playerUUID)); - final ListTag itemsList = new ListTag(); + final NbtList itemsList = new NbtList(); for (ItemEntity item : items) { - final CompoundTag itemTag = item.toTag(new CompoundTag()); - item.writeCustomDataToTag(itemTag); + final NbtCompound itemTag = item.writeNbt(new NbtCompound()); + item.writeCustomDataToNbt(itemTag); itemsList.add(itemTag); } tag.put("Items", itemsList); - final ListTag inventoryList = new ListTag(); - inventory.serialize(inventoryList); + final NbtList inventoryList = new NbtList(); + inventory.writeNbt(inventoryList); tag.put("Inventory", inventoryList); tag.putLong("CreationTime", creationTime); @@ -270,26 +270,26 @@ public CompoundTag toTag(CompoundTag tag) { } /** - * Deserializes a death chest from a {@link CompoundTag}. + * Deserializes a death chest from a {@link NbtCompound}. * * @param world a {@link ServerWorld}. - * @param tag a {@link CompoundTag}. + * @param tag a {@link NbtCompound}. * @return the deserialized {@link DeathChest}. */ @SuppressWarnings("ConstantConditions") - public static DeathChest fromTag(ServerWorld world, CompoundTag tag) { + public static DeathChest readNbt(ServerWorld world, NbtCompound tag) { final List items = new ArrayList<>(); - for (Tag itemTag : tag.getList("Items", NbtType.COMPOUND)) { + for (NbtElement itemTag : tag.getList("Items", NbtType.COMPOUND)) { final ItemEntity item = new ItemEntity(EntityType.ITEM, world); - item.fromTag((CompoundTag) itemTag); - item.readCustomDataFromTag((CompoundTag) itemTag); + item.readNbt((NbtCompound) itemTag); + item.readCustomDataFromNbt((NbtCompound) itemTag); items.add(item); } //We can pass in a null player here because deserialize doesn't use the player. final PlayerInventory inventory = new PlayerInventory(null); - inventory.deserialize(tag.getList("Inventory", NbtType.COMPOUND)); + inventory.readNbt(tag.getList("Inventory", NbtType.COMPOUND)); return new DeathChest( NbtHelper.toUuid(tag.get("Identifier")), world, diff --git a/src/main/java/com/therandomlabs/vanilladeathchest/deathchest/DeathChestAutoRemover.java b/src/main/java/com/therandomlabs/vanilladeathchest/deathchest/DeathChestAutoRemover.java index c1eba18..db8f31c 100644 --- a/src/main/java/com/therandomlabs/vanilladeathchest/deathchest/DeathChestAutoRemover.java +++ b/src/main/java/com/therandomlabs/vanilladeathchest/deathchest/DeathChestAutoRemover.java @@ -24,10 +24,10 @@ package com.therandomlabs.vanilladeathchest.deathchest; import com.therandomlabs.vanilladeathchest.VanillaDeathChest; -import com.therandomlabs.vanilladeathchest.util.ViewerCount; import com.therandomlabs.vanilladeathchest.world.DeathChestsState; import net.minecraft.block.Blocks; import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.ChestBlockEntity; import net.minecraft.block.entity.LockableContainerBlockEntity; import net.minecraft.server.world.ServerWorld; import net.minecraft.util.math.BlockPos; @@ -81,8 +81,8 @@ private static void removeIfEmpty(DeathChest deathChest) { } if (!VanillaDeathChest.config().misc.onlyRemoveClosedEmptyDeathChests || - !(blockEntity instanceof ViewerCount) || - ((ViewerCount) blockEntity).getViewerCount() == 0) { + !(blockEntity instanceof ChestBlockEntity) || + ChestBlockEntity.getPlayersLookingInChestCount(world, blockEntity.getPos()) == 0) { world.setBlockState(pos, Blocks.AIR.getDefaultState()); if (isDoubleChest) { diff --git a/src/main/java/com/therandomlabs/vanilladeathchest/deathchest/DeathChestInteractions.java b/src/main/java/com/therandomlabs/vanilladeathchest/deathchest/DeathChestInteractions.java index c3dcdb5..9b8ca20 100644 --- a/src/main/java/com/therandomlabs/vanilladeathchest/deathchest/DeathChestInteractions.java +++ b/src/main/java/com/therandomlabs/vanilladeathchest/deathchest/DeathChestInteractions.java @@ -68,7 +68,7 @@ public static ActionResult interact( return ActionResult.PASS; } - if (!world.getBlockState(blockHitResult.getBlockPos()).getBlock().hasBlockEntity()) { + if (!world.getBlockState(blockHitResult.getBlockPos()).hasBlockEntity()) { return ActionResult.PASS; } @@ -113,7 +113,7 @@ public static boolean attemptInteract(DeathChest deathChest, ServerPlayerEntity final int amount = config.amountToConsume; if (stack.getItem() == config.item) { - if (amount == 0 || player.abilities.creativeMode) { + if (amount == 0 || player.getAbilities().creativeMode) { deathChest.setLocked(false); return true; } diff --git a/src/main/java/com/therandomlabs/vanilladeathchest/deathchest/DeathChestPlacer.java b/src/main/java/com/therandomlabs/vanilladeathchest/deathchest/DeathChestPlacer.java index 67c496d..be4d27f 100644 --- a/src/main/java/com/therandomlabs/vanilladeathchest/deathchest/DeathChestPlacer.java +++ b/src/main/java/com/therandomlabs/vanilladeathchest/deathchest/DeathChestPlacer.java @@ -54,7 +54,7 @@ import net.minecraft.inventory.Inventories; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; -import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.StringNbtReader; import net.minecraft.server.world.ServerWorld; import net.minecraft.text.LiteralText; @@ -201,13 +201,13 @@ public static void spawnDefenseEntities(DeathChest deathChest) { for (int i = 0; i < config.spawnCount; i++) { //The following spawn logic has been taken from SummonCommand. - CompoundTag tag; + NbtCompound tag; try { tag = StringNbtReader.parse(config.nbtTag); } catch (CommandSyntaxException ex) { //This should not happen. - tag = new CompoundTag(); + tag = new NbtCompound(); } final boolean emptyTag = tag.isEmpty(); @@ -216,7 +216,7 @@ public static void spawnDefenseEntities(DeathChest deathChest) { final Entity entity = EntityType.loadEntityWithPassengers( tag, world, spawnedEntity -> { spawnedEntity.refreshPositionAndAngles( - x, y, z, spawnedEntity.yaw, spawnedEntity.pitch + x, y, z, spawnedEntity.getYaw(), spawnedEntity.getPitch() ); return spawnedEntity; } @@ -247,7 +247,7 @@ private static void placeAndDropRemaining(DeathChest deathChest) { for (ItemEntity drop : allItems) { if (!items.contains(drop)) { - if (drop.removed) { + if (drop.isRemoved()) { world.spawnEntity(new ItemEntity( world, drop.getX(), drop.getY(), drop.getZ(), drop.getStack().copy() )); @@ -375,12 +375,12 @@ private static ContainerConsumptionResult consumeContainerInInventory( continue; } - final CompoundTag tag = stack.getTag(); + final NbtCompound tag = stack.getTag(); if (tag != null) { final DefaultedList inventory = DefaultedList.ofSize(27, ItemStack.EMPTY); - Inventories.fromTag(tag.getCompound("BlockEntityTag"), inventory); + Inventories.readNbt(tag.getCompound("BlockEntityTag"), inventory); //The shulker box must be empty. if (inventory.stream().anyMatch(itemStack -> !itemStack.isEmpty())) { diff --git a/src/main/java/com/therandomlabs/vanilladeathchest/mixin/BlockMixin.java b/src/main/java/com/therandomlabs/vanilladeathchest/mixin/BlockMixin.java index 71b5e13..4f5aaf3 100644 --- a/src/main/java/com/therandomlabs/vanilladeathchest/mixin/BlockMixin.java +++ b/src/main/java/com/therandomlabs/vanilladeathchest/mixin/BlockMixin.java @@ -32,6 +32,7 @@ import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; import net.minecraft.world.World; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; @@ -49,7 +50,7 @@ public final class BlockMixin { private void onBreak( World world, BlockPos pos, BlockState state, PlayerEntity player, CallbackInfo info ) { - if ((Object) this instanceof ShulkerBoxBlock || !state.getBlock().hasBlockEntity()) { + if ((Object) this instanceof ShulkerBoxBlock || !state.hasBlockEntity()) { return; } @@ -62,11 +63,13 @@ private void onBreak( } @Inject( - method = "dropStack", + method = "dropStack(Lnet/minecraft/world/World;" + + "Lnet/minecraft/util/math/BlockPos;" + + "Lnet/minecraft/item/ItemStack;)V", at = @At( value = "INVOKE", - target = "Lnet/minecraft/world/World;" + - "spawnEntity(Lnet/minecraft/entity/Entity;)Z" + target = "Lnet/minecraft/block/Block;dropStack(Lnet/minecraft/world/World;" + + "Ljava/util/function/Supplier;Lnet/minecraft/item/ItemStack;)V" ), cancellable = true ) @@ -78,4 +81,25 @@ private static void dropStack( callback.cancel(); } } + + @Inject( + method = "dropStack(Lnet/minecraft/world/World;" + + "Lnet/minecraft/util/math/BlockPos;" + + "Lnet/minecraft/util/math/Direction;" + + "Lnet/minecraft/item/ItemStack;)V", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/block/Block;dropStack(Lnet/minecraft/world/World;" + + "Ljava/util/function/Supplier;Lnet/minecraft/item/ItemStack;)V" + ), + cancellable = true + ) + private static void dropStack( + World world, BlockPos pos, Direction direction, ItemStack stack, CallbackInfo callback + ) { + if (pos.equals(brokenDeathChest) && !VanillaDeathChest.config().misc.dropDeathChests) { + brokenDeathChest = null; + callback.cancel(); + } + } } diff --git a/src/main/java/com/therandomlabs/vanilladeathchest/mixin/ChestBlockEntityMixin.java b/src/main/java/com/therandomlabs/vanilladeathchest/mixin/ChestBlockEntityAccessor.java similarity index 78% rename from src/main/java/com/therandomlabs/vanilladeathchest/mixin/ChestBlockEntityMixin.java rename to src/main/java/com/therandomlabs/vanilladeathchest/mixin/ChestBlockEntityAccessor.java index 093adad..06d6b74 100644 --- a/src/main/java/com/therandomlabs/vanilladeathchest/mixin/ChestBlockEntityMixin.java +++ b/src/main/java/com/therandomlabs/vanilladeathchest/mixin/ChestBlockEntityAccessor.java @@ -23,22 +23,14 @@ package com.therandomlabs.vanilladeathchest.mixin; -import com.therandomlabs.vanilladeathchest.util.ViewerCount; import net.minecraft.block.entity.ChestBlockEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.util.collection.DefaultedList; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.gen.Accessor; @Mixin(ChestBlockEntity.class) -public final class ChestBlockEntityMixin implements ViewerCount { - @SuppressWarnings("PMD.AvoidProtectedFieldInFinalClass") - @Shadow - protected int viewerCount; - - /** - * {@inheritDoc} - */ - @Override - public int getViewerCount() { - return viewerCount; - } +public interface ChestBlockEntityAccessor { + @Accessor + DefaultedList getInventory(); } diff --git a/src/main/java/com/therandomlabs/vanilladeathchest/mixin/LivingEntityMixin.java b/src/main/java/com/therandomlabs/vanilladeathchest/mixin/LivingEntityMixin.java index 305d292..1e28790 100644 --- a/src/main/java/com/therandomlabs/vanilladeathchest/mixin/LivingEntityMixin.java +++ b/src/main/java/com/therandomlabs/vanilladeathchest/mixin/LivingEntityMixin.java @@ -41,7 +41,7 @@ import net.minecraft.entity.mob.MobEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerInventory; -import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtHelper; import net.minecraft.server.world.ServerWorld; import net.minecraft.util.math.BlockPos; @@ -90,7 +90,7 @@ public void dropHead(CallbackInfo info) { //We can't pass in null here because Campanion mixins into setStack and needs the //player. inventory = new PlayerInventory((PlayerEntity) (Object) this); - final PlayerInventory oldInventory = ((PlayerEntity) (Object) this).inventory; + final PlayerInventory oldInventory = ((PlayerEntity) (Object) this).getInventory(); for (int i = 0; i < oldInventory.size(); i++) { inventory.setStack(i, oldInventory.getStack(i).copy()); @@ -113,7 +113,7 @@ public void dropTail(CallbackInfo info) { return; } - drops.forEach(Entity::remove); + drops.forEach(Entity::discard); final DeathChestsState deathChestsState = DeathChestsState.get(world); final BlockPos pos = entity.getBlockPos(); final DeathChest deathChest = new DeathChest( @@ -180,14 +180,14 @@ public void tick(CallbackInfo info) { squaredDistanceFromPlayer > config.maxSquaredDistanceFromPlayer) { entity.refreshPositionAndAngles( pos.getX() + 0.5, pos.getY() + 1.0, pos.getZ() + 0.5, - entity.yaw, entity.pitch + entity.getYaw(), entity.getPitch() ); } } } - @Inject(method = "writeCustomDataToTag", at = @At("HEAD")) - public void writeCustomDataToTag(CompoundTag tag, CallbackInfo info) { + @Inject(method = "writeCustomDataToNbt", at = @At("HEAD")) + public void writeCustomDataToNbt(NbtCompound tag, CallbackInfo info) { if (deathChestPlayerUUID != null) { if (deathChest != null) { tag.put( @@ -199,8 +199,8 @@ public void writeCustomDataToTag(CompoundTag tag, CallbackInfo info) { } } - @Inject(method = "readCustomDataFromTag", at = @At("HEAD")) - public void readCustomDataFromTag(CompoundTag tag, CallbackInfo info) { + @Inject(method = "readCustomDataFromNbt", at = @At("HEAD")) + public void readCustomDataFromNbt(NbtCompound tag, CallbackInfo info) { if (tag.contains("DeathChestPlayer")) { deathChestPlayerUUID = NbtHelper.toUuid(tag.get("DeathChestPlayer")); diff --git a/src/main/java/com/therandomlabs/vanilladeathchest/mixin/LockableContainerBlockEntityMixin.java b/src/main/java/com/therandomlabs/vanilladeathchest/mixin/LockableContainerBlockEntityMixin.java index 7691b9a..8ca64bd 100644 --- a/src/main/java/com/therandomlabs/vanilladeathchest/mixin/LockableContainerBlockEntityMixin.java +++ b/src/main/java/com/therandomlabs/vanilladeathchest/mixin/LockableContainerBlockEntityMixin.java @@ -23,13 +23,13 @@ package com.therandomlabs.vanilladeathchest.mixin; +import com.therandomlabs.vanilladeathchest.VanillaDeathChest; import com.therandomlabs.vanilladeathchest.deathchest.DeathChest; import com.therandomlabs.vanilladeathchest.util.DeathChestBlockEntity; import com.therandomlabs.vanilladeathchest.world.DeathChestsState; -import net.minecraft.block.BlockState; import net.minecraft.block.entity.BlockEntity; import net.minecraft.block.entity.LockableContainerBlockEntity; -import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtCompound; import net.minecraft.server.world.ServerWorld; import org.checkerframework.checker.nullness.qual.Nullable; import org.spongepowered.asm.mixin.Mixin; @@ -80,15 +80,25 @@ public void markAsDeathChest() { isDeathChest = true; } - @Inject(method = "fromTag", at = @At("TAIL")) - private void fromTag(BlockState state, CompoundTag tag, CallbackInfo info) { - isDeathChest = tag.getBoolean("IsDeathChest"); + @Inject(method = "readNbt", at = @At("TAIL")) + private void readNbt( + NbtCompound nbt, CallbackInfo ci + ) { + isDeathChest = nbt.getBoolean("IsDeathChest"); + VanillaDeathChest.logger.atDebug().log( + "Loading DeathChest at x:" + nbt.getInt("x") + ",y:" + nbt.get("y") + ",z:" + + nbt.get("z")); } - @Inject(method = "toTag", at = @At("TAIL")) - private void toTag(CompoundTag tag, CallbackInfoReturnable info) { + @Inject(method = "writeNbt", at = @At("TAIL")) + private void writeNbt( + NbtCompound nbt, CallbackInfoReturnable cir + ) { if (isDeathChest) { - tag.putBoolean("IsDeathChest", true); + nbt.putBoolean("IsDeathChest", true); + VanillaDeathChest.logger.atDebug().log( + "Storing DeathChest at x:" + nbt.getInt("x") + ",y:" + nbt.get("y") + + ",z:" + nbt.get("z")); } } } diff --git a/src/main/java/com/therandomlabs/vanilladeathchest/mixin/ServerPlayerInteractionManagerMixin.java b/src/main/java/com/therandomlabs/vanilladeathchest/mixin/ServerPlayerInteractionManagerMixin.java index 1dda12d..3a7cb21 100644 --- a/src/main/java/com/therandomlabs/vanilladeathchest/mixin/ServerPlayerInteractionManagerMixin.java +++ b/src/main/java/com/therandomlabs/vanilladeathchest/mixin/ServerPlayerInteractionManagerMixin.java @@ -47,7 +47,7 @@ public final class ServerPlayerInteractionManagerMixin { @Inject(method = "tryBreakBlock", at = @At("HEAD"), cancellable = true) private void tryBreakBlock(BlockPos pos, CallbackInfoReturnable info) { - if (!world.getBlockState(pos).getBlock().hasBlockEntity()) { + if (!world.getBlockState(pos).hasBlockEntity()) { return; } diff --git a/src/main/java/com/therandomlabs/vanilladeathchest/mixin/ShulkerBoxBlockMixin.java b/src/main/java/com/therandomlabs/vanilladeathchest/mixin/ShulkerBoxBlockMixin.java index 1f2a497..3649185 100644 --- a/src/main/java/com/therandomlabs/vanilladeathchest/mixin/ShulkerBoxBlockMixin.java +++ b/src/main/java/com/therandomlabs/vanilladeathchest/mixin/ShulkerBoxBlockMixin.java @@ -63,7 +63,7 @@ private boolean spawnEntity(World world, Entity entity) { } final DefaultedList inventory = DefaultedList.ofSize(27, ItemStack.EMPTY); - Inventories.fromTag( + Inventories.readNbt( ((ItemEntity) entity).getStack().getTag().getCompound("BlockEntityTag"), inventory ); diff --git a/src/main/java/com/therandomlabs/vanilladeathchest/mixin/WorldAccessor.java b/src/main/java/com/therandomlabs/vanilladeathchest/mixin/WorldAccessor.java index 6208abb..8ae9f23 100644 --- a/src/main/java/com/therandomlabs/vanilladeathchest/mixin/WorldAccessor.java +++ b/src/main/java/com/therandomlabs/vanilladeathchest/mixin/WorldAccessor.java @@ -23,15 +23,11 @@ package com.therandomlabs.vanilladeathchest.mixin; -import java.util.List; - -import net.minecraft.block.entity.BlockEntity; import net.minecraft.world.World; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Accessor; @Mixin(World.class) public interface WorldAccessor { - @Accessor - List getUnloadedBlockEntities(); + /*@Accessor + List getUnloadedBlockEntities();*/ } diff --git a/src/main/java/com/therandomlabs/vanilladeathchest/world/DeathChestsState.java b/src/main/java/com/therandomlabs/vanilladeathchest/world/DeathChestsState.java index 9cf6f70..f18b6fd 100644 --- a/src/main/java/com/therandomlabs/vanilladeathchest/world/DeathChestsState.java +++ b/src/main/java/com/therandomlabs/vanilladeathchest/world/DeathChestsState.java @@ -28,20 +28,23 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Objects; import java.util.PriorityQueue; import java.util.Queue; import java.util.Set; import java.util.UUID; import java.util.stream.Collectors; +import com.therandomlabs.vanilladeathchest.VanillaDeathChest; import com.therandomlabs.vanilladeathchest.deathchest.DeathChest; -import com.therandomlabs.vanilladeathchest.mixin.WorldAccessor; +import com.therandomlabs.vanilladeathchest.mixin.ChestBlockEntityAccessor; import com.therandomlabs.vanilladeathchest.util.DeathChestBlockEntity; import net.fabricmc.fabric.api.util.NbtType; import net.minecraft.block.entity.BlockEntity; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.ListTag; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.NbtHelper; +import net.minecraft.nbt.NbtList; import net.minecraft.server.world.ServerWorld; import net.minecraft.util.math.BlockPos; import net.minecraft.world.PersistentState; @@ -58,18 +61,17 @@ public final class DeathChestsState extends PersistentState { new PriorityQueue<>(Comparator.comparing(DeathChest::getCreationTime)); private DeathChestsState(String name, ServerWorld world) { - super(name); + super(); this.world = world; } /** - * {@inheritDoc} + * */ - @Override - public void fromTag(CompoundTag tag) { + public DeathChestsState readNbt(NbtCompound tag) { deathChests.clear(); tag.getList("DeathChests", NbtType.COMPOUND).stream(). - map(deathChestTag -> DeathChest.fromTag(world, (CompoundTag) deathChestTag)). + map(deathChestTag -> DeathChest.readNbt(world, (NbtCompound) deathChestTag)). forEach(deathChest -> deathChests.put(deathChest.getIdentifier(), deathChest)); existingDeathChests.clear(); @@ -80,31 +82,33 @@ public void fromTag(CompoundTag tag) { queuedDeathChests.clear(); tag.getList("QueuedDeathChests", NbtType.COMPOUND).stream(). - map(deathChestTag -> DeathChest.fromTag(world, (CompoundTag) deathChestTag)). + map(deathChestTag -> DeathChest.readNbt(world, (NbtCompound) deathChestTag)). forEach(queuedDeathChests::add); + + return this; } /** * {@inheritDoc} */ @Override - public CompoundTag toTag(CompoundTag tag) { - final ListTag deathChestsList = new ListTag(); + public NbtCompound writeNbt(NbtCompound tag) { + final NbtList deathChestsList = new NbtList(); deathChests.values().stream(). - map(deathChest -> deathChest.toTag(new CompoundTag())). + map(deathChest -> deathChest.writeNbt(new NbtCompound())). forEach(deathChestsList::add); tag.put("DeathChests", deathChestsList); - final ListTag existingDeathChestsList = new ListTag(); + final NbtList existingDeathChestsList = new NbtList(); existingDeathChests.values().stream(). map(DeathChest::getIdentifier). map(NbtHelper::fromUuid). forEach(existingDeathChestsList::add); tag.put("ExistingDeathChests", existingDeathChestsList); - final ListTag queuedDeathChestsList = new ListTag(); + final NbtList queuedDeathChestsList = new NbtList(); queuedDeathChests.stream(). - map(deathChest -> deathChest.toTag(new CompoundTag())). + map(deathChest -> deathChest.writeNbt(new NbtCompound())). forEach(queuedDeathChestsList::add); tag.put("QueuedDeathChests", queuedDeathChestsList); @@ -198,7 +202,8 @@ public Queue getQueuedDeathChests() { */ public static DeathChestsState get(ServerWorld world) { return world.getPersistentStateManager().getOrCreate( - () -> new DeathChestsState("deathchests", world), "deathchests" + (data) -> DeathChestsState.stateFromNbt(world, data), + () -> DeathChestsState.createState(world), "deathchests" ); } @@ -213,13 +218,60 @@ public static void onBlockEntityUnload(BlockEntity blockEntity, ServerWorld worl //when a block entity is removed. The other one is just when a chunk is unloaded. //We can differentiate between these by checking whether the block entity is in //World#unloadedBlockEntities. - if (((WorldAccessor) world).getUnloadedBlockEntities().contains(blockEntity)) { + + // Chunk removal in ClientWorld#unloadBlockEntities no longer relies + // on World#unloadedBlockEntities. + /*if (((WorldAccessor) world).getUnloadedBlockEntities().contains(blockEntity)) { return; - } + }*/ + + // Using a heuristic for chunk unload: If the chest was destroyed (and not chunk unloaded) + // the chest inventory should be empty. if (blockEntity instanceof DeathChestBlockEntity) { final DeathChest deathChest = ((DeathChestBlockEntity) blockEntity).getDeathChest(); - get(world).existingDeathChests.values().remove(deathChest); + final DeathChestsState state = get(world); + final boolean isEmpty; + + if (blockEntity instanceof ChestBlockEntityAccessor) { + isEmpty = ((ChestBlockEntityAccessor) blockEntity).getInventory(). + stream(). + allMatch(ItemStack::isEmpty); + } else { + //not sure if this case can happen, but we keep the chest around + isEmpty = false; + } + + if (deathChest != null) { + VanillaDeathChest.logger.atDebug().log( + "Unload requested for DeathChest at x:" + blockEntity.getPos().getX() + + ",y:" + blockEntity.getPos().getY() + ",z:" + + blockEntity.getPos().getZ() + ",empty:" + isEmpty + ",chestCount:" + + state.existingDeathChests.size()); + } + + if (deathChest != null && isEmpty) { + state.existingDeathChests.values().remove(deathChest); + VanillaDeathChest.logger.atDebug().log( + "Removed DeathChest, " + state.existingDeathChests.size() + " remaining."); + } } } + + /** + * + */ + public static DeathChestsState createState(ServerWorld world) { + //Is name really unused? + DeathChestsState deathChestsState = new DeathChestsState("deathchests", world); + Objects.requireNonNull(deathChestsState); + return deathChestsState; + } + + /** + * + */ + public static DeathChestsState stateFromNbt(ServerWorld world, NbtCompound nbt) { + return createState(world).readNbt(nbt); + } } diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 1040a88..97b14af 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -29,6 +29,6 @@ "depends": { "fabricloader": ">=0.7.4", "fabric": "*", - "minecraft": "1.16.x" + "minecraft": "1.17.x" } } diff --git a/src/main/resources/vanilladeathchest.mixins.json b/src/main/resources/vanilladeathchest.mixins.json index e52bdc9..6ef4f67 100644 --- a/src/main/resources/vanilladeathchest.mixins.json +++ b/src/main/resources/vanilladeathchest.mixins.json @@ -2,10 +2,10 @@ "required": true, "minVersion": "0.8", "package": "com.therandomlabs.vanilladeathchest.mixin", - "compatibilityLevel": "JAVA_8", + "compatibilityLevel": "JAVA_16", "mixins": [ "BlockMixin", - "ChestBlockEntityMixin", + "ChestBlockEntityAccessor", "ExplosionMixin", "LivingEntityMixin", "LockableContainerBlockEntityMixin",