diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 9c86017f9a..1b7db1dbe8 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -27,6 +27,7 @@ body: description: Which server version version you using? If your server version is not listed, it is not supported. Update to a supported version first. multiple: false options: + - '26.1' - '1.21.11' - '1.21.10' - '1.21.8' diff --git a/.github/workflows/upload-release-assets.yml b/.github/workflows/upload-release-assets.yml index 42afa9d2d9..b88cde1211 100644 --- a/.github/workflows/upload-release-assets.yml +++ b/.github/workflows/upload-release-assets.yml @@ -19,7 +19,7 @@ jobs: - name: Clean Build run: ./gradlew clean build --no-daemon - name: Upload Release Assets - uses: AButler/upload-release-assets@v3.0 + uses: AButler/upload-release-assets@v4.0.0 with: files: 'worldedit-bukkit/build/libs/FastAsyncWorldEdit-*.jar' repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/build-logic/src/main/kotlin/buildlogic.adapter.gradle.kts b/build-logic/src/main/kotlin/buildlogic.adapter.gradle.kts index 4fe0aa6b45..5a4e4ad7e2 100644 --- a/build-logic/src/main/kotlin/buildlogic.adapter.gradle.kts +++ b/build-logic/src/main/kotlin/buildlogic.adapter.gradle.kts @@ -8,6 +8,8 @@ plugins { id("io.papermc.paperweight.userdev") } +val requiresReobfJar = project.name.startsWith("adapter-1_") + paperweight { injectPaperRepository = false reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArtifactConfiguration.REOBF_PRODUCTION @@ -18,23 +20,23 @@ repositories { name = "PaperMC" url = uri("https://repo.papermc.io/repository/maven-public/") content { - excludeModule("io.papermc.paper", "dev-bundle") + // excludeModule("io.papermc.paper", "dev-bundle") } } maven { name = "EngineHub Repository" url = uri("https://maven.enginehub.org/repo/") content { - excludeModule("io.papermc.paper", "dev-bundle") + // excludeModule("io.papermc.paper", "dev-bundle") } } - maven { +/* maven { name = "IntellectualSites" url = uri("https://repo.intellectualsites.dev/repository/paper-dev-bundles/") content { - includeModule("io.papermc.paper", "dev-bundle") + // includeModule("io.papermc.paper", "dev-bundle") } - } + }*/ mavenCentral() afterEvaluate { killNonEngineHubRepositories() @@ -52,8 +54,15 @@ dependencies { } } +java { + // Required when we de-sync release option and declared Java versions. + disableAutoTargetJvm() +} + tasks.named("assemble") { - dependsOn("reobfJar") + if (requiresReobfJar) { + dependsOn("reobfJar") + } } tasks.named("javadoc") { diff --git a/build.gradle.kts b/build.gradle.kts index 37ea2f31e1..de0492d4fe 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,11 +8,11 @@ plugins { alias(libs.plugins.codecov) jacoco id("buildlogic.common") - id("com.gradleup.nmcp.aggregation") version "1.4.4" + id("com.gradleup.nmcp.aggregation") version "1.5.0" id("xyz.jpenilla.run-paper") version "3.0.2" } -var rootVersion by extra("2.15.1") +var rootVersion by extra("2.15.2") var snapshot by extra("SNAPSHOT") var revision: String by extra("") var buildNumber by extra("") @@ -94,7 +94,7 @@ allprojects { } val supportedVersions: List = listOf("1.20.4", "1.20.5", "1.20.6", "1.21", "1.21.1", "1.21.4", "1.21.5", - "1.21.8", "1.21.10", "1.21.11") + "1.21.8", "1.21.10", "1.21.11", "26.1.2") tasks { supportedVersions.forEach { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fe451fb079..d188e3ac2c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,7 +13,7 @@ worldguard-bukkit = "7.0.15" griefprevention = "18.0.0" griefdefender = "2.1.0-SNAPSHOT" residence = "6.0.0.1" -towny = "0.102.0.13" +towny = "0.103.0.0" plotsquared = "7.5.12" # Third party @@ -21,7 +21,7 @@ bstats = "3.2.1" sparsebitset = "1.3" parallelgzip = "1.0.5" adventure = "4.26.1" -checkerqual = "4.0.0" +checkerqual = "4.1.0" truezip = "6.8.4" auto-value = "1.11.1" jsr305 = "3.0.2" diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d997cfc60f..b1b8ef56b4 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c82ad3ff01..e74c8700b2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,9 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-all.zip networkTimeout=10000 +retries=0 +retryBackOffMs=500 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 739907dfd1..b9bb139f79 100755 --- a/gradlew +++ b/gradlew @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. diff --git a/gradlew.bat b/gradlew.bat index e509b2dd8f..58d9826829 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -23,8 +23,8 @@ @rem @rem ########################################################################## -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @@ -51,7 +51,7 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -65,7 +65,7 @@ echo. 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo location of your Java installation. 1>&2 -goto fail +"%COMSPEC%" /c exit 1 :execute @rem Setup the command line @@ -73,21 +73,10 @@ goto fail @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel + +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/settings.gradle.kts b/settings.gradle.kts index 77b27b66d9..b19302e42b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -55,7 +55,7 @@ includeBuild("build-logic") include("worldedit-libs") -listOf("1_20_2", "1_20_4", "1_20_5", "1_21", "1_21_4", "1_21_5", "1_21_6", "1_21_9", "1_21_11").forEach { +listOf("1_20_2", "1_20_4", "1_20_5", "1_21", "1_21_4", "1_21_5", "1_21_6", "1_21_9", "1_21_11", "26.1").forEach { include("worldedit-bukkit:adapters:adapter-$it") } @@ -66,4 +66,4 @@ listOf("bukkit", "core", "cli").forEach { include("worldedit-libs:core:ap") -enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") +// enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") diff --git a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R2/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R2/PaperweightAdapter.java index 28391ee583..ce2332b925 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R2/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R2/PaperweightAdapter.java @@ -68,10 +68,12 @@ import com.sk89q.worldedit.world.block.BlockTypes; import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; import com.sk89q.worldedit.world.generation.StructureType; +import com.sk89q.worldedit.world.generation.TreeType; import com.sk89q.worldedit.world.item.ItemType; import net.minecraft.Util; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; +import net.minecraft.core.HolderLookup; import net.minecraft.core.HolderSet; import net.minecraft.core.Registry; import net.minecraft.core.SectionPos; @@ -113,6 +115,9 @@ import net.minecraft.world.level.dimension.LevelStem; import net.minecraft.world.level.levelgen.WorldOptions; import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.feature.CoralTreeFeature; +import net.minecraft.world.level.levelgen.feature.TreeFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -918,6 +923,20 @@ public void initializeRegistries() { } } + // Trees + HolderLookup.RegistryLookup placedFeatureRegistry = server.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE); + placedFeatureRegistry.listElements() + .filter(feature -> { + var underlyingFeature = feature.value().feature().value().feature(); + return underlyingFeature instanceof TreeFeature || underlyingFeature instanceof CoralTreeFeature; + }) + .forEach(feature -> { + String key = feature.key().toString(); + if (TreeType.REGISTRY.get(key) == null) { + TreeType.REGISTRY.register(key, new TreeType(key)); + } + }); + // BiomeCategories Registry biomeRegistry = server.registryAccess().registryOrThrow(Registries.BIOME); biomeRegistry.getTagNames().forEach(tagKey -> { diff --git a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightFaweAdapter.java index ec2adc4cc4..69bc71f215 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightFaweAdapter.java @@ -20,6 +20,7 @@ import com.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.WorldEditPlugin; @@ -53,6 +54,7 @@ import com.sk89q.worldedit.world.entity.EntityType; import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; import com.sk89q.worldedit.world.generation.StructureType; +import com.sk89q.worldedit.world.generation.TreeType; import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BlockMaterial; import io.papermc.lib.PaperLib; @@ -90,6 +92,7 @@ import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -692,6 +695,44 @@ public boolean generateStructure(StructureType type, World world, EditSession ed //FAWE end } + @Override + public boolean generateTree( + final TreeType treeType, + final World world, + final EditSession session, + final BlockVector3 pt + ) throws MaxChangedBlocksException { + ServerLevel serverLevel = getServerLevel(world); + ChunkGenerator generator = serverLevel.getMinecraftWorld().getChunkSource().getGenerator(); + + PlacedFeature placedFeature = serverLevel + .registryAccess() + .registryOrThrow(Registries.PLACED_FEATURE) + .get(ResourceLocation.tryParse(treeType.id())); + + FaweBlockStateListPopulator populator = new FaweBlockStateListPopulator(serverLevel); + List placed = TaskManager.taskManager().sync(() -> { + preCaptureStates(serverLevel); + try { + if (!placedFeature.place( + populator, + generator, + serverLevel.random, + new BlockPos(pt.x(), pt.y(), pt.z()) + )) { + return null; + } + List placedBlocks = new ArrayList<>(populator.getList()); + placedBlocks.addAll(serverLevel.capturedBlockStates.values()); + return placedBlocks; + } finally { + postCaptureBlockStates(serverLevel); + } + }); + + return placeFeatureIntoSession(session, populator, placed); + } + private boolean placeFeatureIntoSession( final EditSession editSession, final FaweBlockStateListPopulator populator, diff --git a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightPlacementStateProcessor.java b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightPlacementStateProcessor.java index 24b0fe129d..e87757fd1b 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightPlacementStateProcessor.java +++ b/worldedit-bukkit/adapters/adapter-1_20_2/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R2/PaperweightPlacementStateProcessor.java @@ -34,11 +34,7 @@ public class PaperweightPlacementStateProcessor extends PlacementStateProcessor public PaperweightPlacementStateProcessor(Extent extent, BlockTypeMask mask, Region region) { super(extent, mask, region); - World world = ExtentTraverser.getWorldFromExtent(extent); - if (world == null) { - throw new UnsupportedOperationException( - "World is required for PlacementStateProcessor but none found in given extent."); - } + World world = getWorldFromExtent(extent); BukkitWorld bukkitWorld; if (world instanceof WorldWrapper wrapper) { bukkitWorld = (BukkitWorld) wrapper.getParent(); diff --git a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R3/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R3/PaperweightAdapter.java index f524676068..f4562764bc 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R3/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_20_R3/PaperweightAdapter.java @@ -68,10 +68,12 @@ import com.sk89q.worldedit.world.block.BlockTypes; import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; import com.sk89q.worldedit.world.generation.StructureType; +import com.sk89q.worldedit.world.generation.TreeType; import com.sk89q.worldedit.world.item.ItemType; import net.minecraft.Util; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; +import net.minecraft.core.HolderLookup; import net.minecraft.core.HolderSet; import net.minecraft.core.Registry; import net.minecraft.core.SectionPos; @@ -113,6 +115,9 @@ import net.minecraft.world.level.dimension.LevelStem; import net.minecraft.world.level.levelgen.WorldOptions; import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.feature.CoralTreeFeature; +import net.minecraft.world.level.levelgen.feature.TreeFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -917,6 +922,20 @@ public void initializeRegistries() { } } + // Trees + HolderLookup.RegistryLookup placedFeatureRegistry = server.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE); + placedFeatureRegistry.listElements() + .filter(feature -> { + var underlyingFeature = feature.value().feature().value().feature(); + return underlyingFeature instanceof TreeFeature || underlyingFeature instanceof CoralTreeFeature; + }) + .forEach(feature -> { + String key = feature.key().toString(); + if (TreeType.REGISTRY.get(key) == null) { + TreeType.REGISTRY.register(key, new TreeType(key)); + } + }); + // BiomeCategories Registry biomeRegistry = server.registryAccess().registryOrThrow(Registries.BIOME); biomeRegistry.getTagNames().forEach(tagKey -> { diff --git a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightFaweAdapter.java index b0f7c7e1a9..3b428dd6b1 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightFaweAdapter.java @@ -20,6 +20,7 @@ import com.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.WorldEditPlugin; @@ -52,6 +53,7 @@ import com.sk89q.worldedit.world.entity.EntityType; import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; import com.sk89q.worldedit.world.generation.StructureType; +import com.sk89q.worldedit.world.generation.TreeType; import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BlockMaterial; import io.papermc.lib.PaperLib; @@ -89,6 +91,7 @@ import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -691,6 +694,44 @@ public boolean generateStructure(StructureType type, World world, EditSession ed //FAWE end } + @Override + public boolean generateTree( + final TreeType treeType, + final World world, + final EditSession session, + final BlockVector3 pt + ) throws MaxChangedBlocksException { + ServerLevel serverLevel = getServerLevel(world); + ChunkGenerator generator = serverLevel.getMinecraftWorld().getChunkSource().getGenerator(); + + PlacedFeature placedFeature = serverLevel + .registryAccess() + .registryOrThrow(Registries.PLACED_FEATURE) + .get(ResourceLocation.tryParse(treeType.id())); + + FaweBlockStateListPopulator populator = new FaweBlockStateListPopulator(serverLevel); + List placed = TaskManager.taskManager().sync(() -> { + preCaptureStates(serverLevel); + try { + if (!placedFeature.place( + populator, + generator, + serverLevel.random, + new BlockPos(pt.x(), pt.y(), pt.z()) + )) { + return null; + } + List placedBlocks = new ArrayList<>(populator.getList()); + placedBlocks.addAll(serverLevel.capturedBlockStates.values()); + return placedBlocks; + } finally { + postCaptureBlockStates(serverLevel); + } + }); + + return placeFeatureIntoSession(session, populator, placed); + } + private boolean placeFeatureIntoSession( final EditSession editSession, final FaweBlockStateListPopulator populator, diff --git a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightPlacementStateProcessor.java b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightPlacementStateProcessor.java index fd12e5f380..11d2b0c783 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightPlacementStateProcessor.java +++ b/worldedit-bukkit/adapters/adapter-1_20_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R3/PaperweightPlacementStateProcessor.java @@ -34,11 +34,7 @@ public class PaperweightPlacementStateProcessor extends PlacementStateProcessor public PaperweightPlacementStateProcessor(Extent extent, BlockTypeMask mask, Region region) { super(extent, mask, region); - World world = ExtentTraverser.getWorldFromExtent(extent); - if (world == null) { - throw new UnsupportedOperationException( - "World is required for PlacementStateProcessor but none found in given extent."); - } + World world = getWorldFromExtent(extent); BukkitWorld bukkitWorld; if (world instanceof WorldWrapper wrapper) { bukkitWorld = (BukkitWorld) wrapper.getParent(); diff --git a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R4/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R4/PaperweightAdapter.java index cb6da03c34..47785f4a9e 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R4/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext.fawe/v1_20_R4/PaperweightAdapter.java @@ -68,10 +68,12 @@ import com.sk89q.worldedit.world.block.BlockTypes; import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; import com.sk89q.worldedit.world.generation.StructureType; +import com.sk89q.worldedit.world.generation.TreeType; import com.sk89q.worldedit.world.item.ItemType; import net.minecraft.Util; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; +import net.minecraft.core.HolderLookup; import net.minecraft.core.HolderSet; import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; @@ -117,6 +119,9 @@ import net.minecraft.world.level.dimension.LevelStem; import net.minecraft.world.level.levelgen.WorldOptions; import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.feature.CoralTreeFeature; +import net.minecraft.world.level.levelgen.feature.TreeFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -941,6 +946,20 @@ public void initializeRegistries() { } } + // Trees + HolderLookup.RegistryLookup placedFeatureRegistry = server.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE); + placedFeatureRegistry.listElements() + .filter(feature -> { + var underlyingFeature = feature.value().feature().value().feature(); + return underlyingFeature instanceof TreeFeature || underlyingFeature instanceof CoralTreeFeature; + }) + .forEach(feature -> { + String key = feature.key().toString(); + if (TreeType.REGISTRY.get(key) == null) { + TreeType.REGISTRY.register(key, new TreeType(key)); + } + }); + // BiomeCategories Registry biomeRegistry = server.registryAccess().registryOrThrow(Registries.BIOME); biomeRegistry.getTagNames().forEach(tagKey -> { diff --git a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightFaweAdapter.java index e1ea192890..e2526fb505 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightFaweAdapter.java @@ -21,6 +21,7 @@ import com.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.WorldEditPlugin; @@ -54,6 +55,7 @@ import com.sk89q.worldedit.world.entity.EntityType; import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; import com.sk89q.worldedit.world.generation.StructureType; +import com.sk89q.worldedit.world.generation.TreeType; import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BlockMaterial; import io.papermc.lib.PaperLib; @@ -94,6 +96,7 @@ import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -705,6 +708,44 @@ public boolean generateStructure(StructureType type, World world, EditSession ed //FAWE end } + @Override + public boolean generateTree( + final TreeType treeType, + final World world, + final EditSession session, + final BlockVector3 pt + ) throws MaxChangedBlocksException { + ServerLevel serverLevel = getServerLevel(world); + ChunkGenerator generator = serverLevel.getMinecraftWorld().getChunkSource().getGenerator(); + + PlacedFeature placedFeature = serverLevel + .registryAccess() + .registryOrThrow(Registries.PLACED_FEATURE) + .get(ResourceLocation.tryParse(treeType.id())); + + FaweBlockStateListPopulator populator = new FaweBlockStateListPopulator(serverLevel); + List placed = TaskManager.taskManager().sync(() -> { + preCaptureStates(serverLevel); + try { + if (!placedFeature.place( + populator, + generator, + serverLevel.random, + new BlockPos(pt.x(), pt.y(), pt.z()) + )) { + return null; + } + List placedBlocks = new ArrayList<>(populator.getList()); + placedBlocks.addAll(serverLevel.capturedBlockStates.values()); + return placedBlocks; + } finally { + postCaptureBlockStates(serverLevel); + } + }); + + return placeFeatureIntoSession(session, populator, placed); + } + private boolean placeFeatureIntoSession( final EditSession editSession, final FaweBlockStateListPopulator populator, diff --git a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightPlacementStateProcessor.java b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightPlacementStateProcessor.java index cf52c30cf8..720d0547db 100644 --- a/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightPlacementStateProcessor.java +++ b/worldedit-bukkit/adapters/adapter-1_20_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_20_R4/PaperweightPlacementStateProcessor.java @@ -35,11 +35,7 @@ public class PaperweightPlacementStateProcessor extends PlacementStateProcessor public PaperweightPlacementStateProcessor(Extent extent, BlockTypeMask mask, Region region) { super(extent, mask, region); - World world = ExtentTraverser.getWorldFromExtent(extent); - if (world == null) { - throw new UnsupportedOperationException( - "World is required for PlacementStateProcessor but none found in given extent."); - } + World world = getWorldFromExtent(extent); BukkitWorld bukkitWorld; if (world instanceof WorldWrapper wrapper) { bukkitWorld = (BukkitWorld) wrapper.getParent(); diff --git a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_R1/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_R1/PaperweightAdapter.java index 5e8ed03d7e..6f6cd435a2 100644 --- a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_R1/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_R1/PaperweightAdapter.java @@ -29,6 +29,7 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.Lifecycle; import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.blocks.BaseItem; import com.sk89q.worldedit.blocks.BaseItemStack; @@ -68,11 +69,13 @@ import com.sk89q.worldedit.world.entity.EntityTypes; import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; import com.sk89q.worldedit.world.generation.StructureType; +import com.sk89q.worldedit.world.generation.TreeType; import com.sk89q.worldedit.world.item.ItemType; import net.minecraft.SharedConstants; import net.minecraft.Util; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; +import net.minecraft.core.HolderLookup; import net.minecraft.core.HolderSet; import net.minecraft.core.Registry; import net.minecraft.core.component.DataComponentPatch; @@ -117,6 +120,10 @@ import net.minecraft.world.level.dimension.LevelStem; import net.minecraft.world.level.levelgen.WorldOptions; import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.feature.CoralTreeFeature; +import net.minecraft.world.level.levelgen.feature.Feature; +import net.minecraft.world.level.levelgen.feature.TreeFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -927,6 +934,20 @@ public void initializeRegistries() { } } + // Trees + HolderLookup.RegistryLookup placedFeatureRegistry = server.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE); + placedFeatureRegistry.listElements() + .filter(feature -> { + var underlyingFeature = feature.value().feature().value().feature(); + return underlyingFeature instanceof TreeFeature || underlyingFeature instanceof CoralTreeFeature; + }) + .forEach(feature -> { + String key = feature.key().toString(); + if (TreeType.REGISTRY.get(key) == null) { + TreeType.REGISTRY.register(key, new TreeType(key)); + } + }); + // BiomeCategories Registry biomeRegistry = server.registryAccess().registryOrThrow(Registries.BIOME); biomeRegistry.getTagNames().forEach(tagKey -> { @@ -957,6 +978,16 @@ public void sendBiomeUpdates(World world, Iterable chunks) { } @Override + public boolean generateTree(TreeType treeType, World world, EditSession session, BlockVector3 pt) throws MaxChangedBlocksException { + ServerLevel originalWorld = ((CraftWorld) world).getHandle(); + PlacedFeature k = originalWorld.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE) + .getOrThrow(ResourceKey.create(Registries.PLACED_FEATURE, ResourceLocation.tryParse(treeType.id()))) + .value(); + ServerChunkCache chunkManager = originalWorld.getChunkSource(); + WorldGenLevel proxyLevel = PaperweightServerLevelDelegateProxy.newInstance(session, originalWorld, this); + return k != null && k.place(proxyLevel, chunkManager.getGenerator(), random, new BlockPos(pt.x(), pt.y(), pt.z())); + } + public boolean generateFeature(ConfiguredFeatureType type, World world, EditSession session, BlockVector3 pt) { ServerLevel originalWorld = ((CraftWorld) world).getHandle(); ConfiguredFeature k = originalWorld.registryAccess().registryOrThrow(Registries.CONFIGURED_FEATURE).get(ResourceLocation.tryParse(type.id())); diff --git a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightFaweAdapter.java index a17f3b89da..61a012a02d 100644 --- a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightFaweAdapter.java @@ -21,6 +21,7 @@ import com.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.WorldEditPlugin; @@ -54,6 +55,7 @@ import com.sk89q.worldedit.world.entity.EntityType; import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; import com.sk89q.worldedit.world.generation.StructureType; +import com.sk89q.worldedit.world.generation.TreeType; import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BlockMaterial; import io.papermc.lib.PaperLib; @@ -94,6 +96,7 @@ import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -706,6 +709,44 @@ public boolean generateStructure(StructureType type, World world, EditSession ed //FAWE end } + @Override + public boolean generateTree( + final TreeType treeType, + final World world, + final EditSession session, + final BlockVector3 pt + ) throws MaxChangedBlocksException { + ServerLevel serverLevel = getServerLevel(world); + ChunkGenerator generator = serverLevel.getMinecraftWorld().getChunkSource().getGenerator(); + + PlacedFeature placedFeature = serverLevel + .registryAccess() + .registryOrThrow(Registries.PLACED_FEATURE) + .get(ResourceLocation.tryParse(treeType.id())); + + FaweBlockStateListPopulator populator = new FaweBlockStateListPopulator(serverLevel); + List placed = TaskManager.taskManager().sync(() -> { + preCaptureStates(serverLevel); + try { + if (!placedFeature.place( + populator, + generator, + serverLevel.random, + new BlockPos(pt.x(), pt.y(), pt.z()) + )) { + return null; + } + List placedBlocks = new ArrayList<>(populator.getList()); + placedBlocks.addAll(serverLevel.capturedBlockStates.values()); + return placedBlocks; + } finally { + postCaptureBlockStates(serverLevel); + } + }); + + return placeFeatureIntoSession(session, populator, placed); + } + private boolean placeFeatureIntoSession( final EditSession editSession, final FaweBlockStateListPopulator populator, diff --git a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightPlacementStateProcessor.java b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightPlacementStateProcessor.java index 14ae451a6c..b2b6c074f1 100644 --- a/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightPlacementStateProcessor.java +++ b/worldedit-bukkit/adapters/adapter-1_21/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_R1/PaperweightPlacementStateProcessor.java @@ -35,11 +35,7 @@ public class PaperweightPlacementStateProcessor extends PlacementStateProcessor public PaperweightPlacementStateProcessor(Extent extent, BlockTypeMask mask, Region region) { super(extent, mask, region); - World world = ExtentTraverser.getWorldFromExtent(extent); - if (world == null) { - throw new UnsupportedOperationException( - "World is required for PlacementStateProcessor but none found in given extent."); - } + World world = getWorldFromExtent(extent); BukkitWorld bukkitWorld; if (world instanceof WorldWrapper wrapper) { bukkitWorld = (BukkitWorld) wrapper.getParent(); diff --git a/worldedit-bukkit/adapters/adapter-1_21_11/build.gradle.kts b/worldedit-bukkit/adapters/adapter-1_21_11/build.gradle.kts index d61b503251..4e9dcdda20 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_11/build.gradle.kts +++ b/worldedit-bukkit/adapters/adapter-1_21_11/build.gradle.kts @@ -6,6 +6,6 @@ plugins { dependencies { // https://repo.papermc.io/service/rest/repository/browse/maven-public/io/papermc/paper/dev-bundle/ - the().paperDevBundle("1.21.11-R0.1-20251223.192256-16") + the().paperDevBundle("1.21.11-R0.1-20260310.030221-86") compileOnly(libs.paperLib) } diff --git a/worldedit-bukkit/adapters/adapter-1_21_11/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_11/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1_21_11/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_11/PaperweightAdapter.java index 57e5a6cc8a..f1d65c8da8 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_11/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_11/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21_11/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_11/PaperweightAdapter.java @@ -70,6 +70,7 @@ import com.sk89q.worldedit.world.entity.EntityTypes; import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; import com.sk89q.worldedit.world.generation.StructureType; +import com.sk89q.worldedit.world.generation.TreeType; import com.sk89q.worldedit.world.item.ItemType; import net.minecraft.SharedConstants; import net.minecraft.core.BlockPos; @@ -130,6 +131,10 @@ import net.minecraft.world.level.dimension.LevelStem; import net.minecraft.world.level.levelgen.WorldOptions; import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.feature.CoralTreeFeature; +import net.minecraft.world.level.levelgen.feature.FallenTreeFeature; +import net.minecraft.world.level.levelgen.feature.TreeFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -902,6 +907,19 @@ public void initializeRegistries() { } } + // Trees + Registry placedFeatureRegistry = server.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE); + for (Identifier name : placedFeatureRegistry.keySet()) { + // Do some hackery to make sure this is a tree + var underlyingFeature = placedFeatureRegistry.get(name).get().value().feature().value().feature(); + if (underlyingFeature instanceof TreeFeature || underlyingFeature instanceof FallenTreeFeature || underlyingFeature instanceof CoralTreeFeature) { + String key = name.toString(); + if (TreeType.REGISTRY.get(key) == null) { + TreeType.REGISTRY.register(key, new TreeType(key)); + } + } + } + // BiomeCategories Registry biomeRegistry = server.registryAccess().lookupOrThrow(Registries.BIOME); biomeRegistry.getTags().forEach(tag -> { @@ -920,6 +938,17 @@ public void initializeRegistries() { }); } + @Override + public boolean generateTree(TreeType treeType, World world, EditSession session, BlockVector3 pt) throws MaxChangedBlocksException { + ServerLevel originalWorld = ((CraftWorld) world).getHandle(); + PlacedFeature feature = originalWorld.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE).getValue(Identifier.tryParse(treeType.id())); + ServerChunkCache chunkManager = originalWorld.getChunkSource(); + try (PaperweightServerLevelDelegateProxy.LevelAndProxy proxyLevel = + PaperweightServerLevelDelegateProxy.newInstance(session, originalWorld, this)) { + return feature != null && feature.place(proxyLevel.level(), chunkManager.getGenerator(), random, new BlockPos(pt.x(), pt.y(), pt.z())); + } + } + public boolean generateFeature(ConfiguredFeatureType type, World world, EditSession session, BlockVector3 pt) { ServerLevel originalWorld = ((CraftWorld) world).getHandle(); ConfiguredFeature feature = originalWorld.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).getValue(Identifier.tryParse(type.id())); diff --git a/worldedit-bukkit/adapters/adapter-1_21_11/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_11/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_21_11/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_11/PaperweightFaweAdapter.java index e7e647f6e0..bfedad2f93 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_11/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_11/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21_11/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_11/PaperweightFaweAdapter.java @@ -19,6 +19,7 @@ import com.mojang.serialization.Codec; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.WorldEditPlugin; @@ -52,6 +53,7 @@ import com.sk89q.worldedit.world.entity.EntityType; import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; import com.sk89q.worldedit.world.generation.StructureType; +import com.sk89q.worldedit.world.generation.TreeType; import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BlockMaterial; import io.papermc.lib.PaperLib; @@ -92,6 +94,7 @@ import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -677,6 +680,44 @@ public boolean generateStructure(StructureType type, World world, EditSession ed return placeFeatureIntoSession(editSession, populator, placed); } + @Override + public boolean generateTree( + final TreeType treeType, + final World world, + final EditSession session, + final BlockVector3 pt + ) throws MaxChangedBlocksException { + ServerLevel serverLevel = getServerLevel(world); + ChunkGenerator generator = serverLevel.getMinecraftWorld().getChunkSource().getGenerator(); + + PlacedFeature placedFeature = serverLevel + .registryAccess() + .lookupOrThrow(Registries.PLACED_FEATURE) + .getValue(Identifier.tryParse(treeType.id())); + + FaweBlockStateListPopulator populator = new FaweBlockStateListPopulator(serverLevel); + List placed = TaskManager.taskManager().sync(() -> { + preCaptureStates(serverLevel); + try { + if (!placedFeature.place( + populator, + generator, + serverLevel.random, + new BlockPos(pt.x(), pt.y(), pt.z()) + )) { + return null; + } + List placedBlocks = new ArrayList<>(populator.getSnapshotBlocks()); + placedBlocks.addAll(serverLevel.capturedBlockStates.values()); + return placedBlocks; + } finally { + postCaptureBlockStates(serverLevel); + } + }); + + return placeFeatureIntoSession(session, populator, placed); + } + private boolean placeFeatureIntoSession( final EditSession editSession, final FaweBlockStateListPopulator populator, diff --git a/worldedit-bukkit/adapters/adapter-1_21_11/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_11/PaperweightPlacementStateProcessor.java b/worldedit-bukkit/adapters/adapter-1_21_11/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_11/PaperweightPlacementStateProcessor.java index 6a77322a67..aea0458cc7 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_11/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_11/PaperweightPlacementStateProcessor.java +++ b/worldedit-bukkit/adapters/adapter-1_21_11/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_11/PaperweightPlacementStateProcessor.java @@ -35,11 +35,7 @@ public class PaperweightPlacementStateProcessor extends PlacementStateProcessor public PaperweightPlacementStateProcessor(Extent extent, BlockTypeMask mask, Region region) { super(extent, mask, region); - World world = ExtentTraverser.getWorldFromExtent(extent); - if (world == null) { - throw new UnsupportedOperationException( - "World is required for PlacementStateProcessor but none found in given extent."); - } + World world = getWorldFromExtent(extent); BukkitWorld bukkitWorld; if (world instanceof WorldWrapper wrapper) { bukkitWorld = (BukkitWorld) wrapper.getParent(); diff --git a/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_4/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_4/PaperweightAdapter.java index 01987f18cb..70fbe27937 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_4/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_4/PaperweightAdapter.java @@ -65,6 +65,7 @@ import com.sk89q.worldedit.world.entity.EntityTypes; import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; import com.sk89q.worldedit.world.generation.StructureType; +import com.sk89q.worldedit.world.generation.TreeType; import com.sk89q.worldedit.world.item.ItemType; import net.minecraft.SharedConstants; import net.minecraft.Util; @@ -112,6 +113,9 @@ import net.minecraft.world.level.dimension.LevelStem; import net.minecraft.world.level.levelgen.WorldOptions; import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.feature.CoralTreeFeature; +import net.minecraft.world.level.levelgen.feature.TreeFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -879,6 +883,19 @@ public void initializeRegistries() { } } + // Trees + Registry placedFeatureRegistry = server.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE); + for (ResourceLocation name : placedFeatureRegistry.keySet()) { + // Do some hackery to make sure this is a tree + var underlyingFeature = placedFeatureRegistry.get(name).get().value().feature().value().feature(); + if (underlyingFeature instanceof TreeFeature || underlyingFeature instanceof CoralTreeFeature) { + String key = name.toString(); + if (TreeType.REGISTRY.get(key) == null) { + TreeType.REGISTRY.register(key, new TreeType(key)); + } + } + } + // BiomeCategories Registry biomeRegistry = server.registryAccess().lookupOrThrow(Registries.BIOME); biomeRegistry.getTags().forEach(tag -> { @@ -908,6 +925,17 @@ public void sendBiomeUpdates(World world, Iterable chunks) { originalWorld.getChunkSource().chunkMap.resendBiomesForChunks(nativeChunks); } + @Override + public boolean generateTree(TreeType treeType, World world, EditSession session, BlockVector3 pt) throws MaxChangedBlocksException { + ServerLevel originalWorld = ((CraftWorld) world).getHandle(); + PlacedFeature feature = originalWorld.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE).getValue(ResourceLocation.tryParse(treeType.id())); + ServerChunkCache chunkManager = originalWorld.getChunkSource(); + try (PaperweightServerLevelDelegateProxy.LevelAndProxy proxyLevel = + PaperweightServerLevelDelegateProxy.newInstance(session, originalWorld, this)) { + return feature != null && feature.place(proxyLevel.level(), chunkManager.getGenerator(), random, new BlockPos(pt.x(), pt.y(), pt.z())); + } + } + public boolean generateFeature(ConfiguredFeatureType type, World world, EditSession session, BlockVector3 pt) { ServerLevel originalWorld = ((CraftWorld) world).getHandle(); ConfiguredFeature feature = originalWorld.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).getValue(ResourceLocation.tryParse(type.id())); diff --git a/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightFaweAdapter.java index d13d2083c9..137f9500cb 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightFaweAdapter.java @@ -19,6 +19,7 @@ import com.google.common.collect.Sets; import com.mojang.serialization.Codec; import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.WorldEditPlugin; @@ -52,6 +53,7 @@ import com.sk89q.worldedit.world.entity.EntityType; import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; import com.sk89q.worldedit.world.generation.StructureType; +import com.sk89q.worldedit.world.generation.TreeType; import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BlockMaterial; import io.papermc.lib.PaperLib; @@ -93,6 +95,7 @@ import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -688,6 +691,44 @@ public boolean generateStructure(StructureType type, World world, EditSession ed return placeFeatureIntoSession(editSession, populator, placed); } + @Override + public boolean generateTree( + final TreeType treeType, + final World world, + final EditSession session, + final BlockVector3 pt + ) throws MaxChangedBlocksException { + ServerLevel serverLevel = getServerLevel(world); + ChunkGenerator generator = serverLevel.getMinecraftWorld().getChunkSource().getGenerator(); + + PlacedFeature placedFeature = serverLevel + .registryAccess() + .lookupOrThrow(Registries.PLACED_FEATURE) + .getValue(ResourceLocation.tryParse(treeType.id())); + + FaweBlockStateListPopulator populator = new FaweBlockStateListPopulator(serverLevel); + List placed = TaskManager.taskManager().sync(() -> { + preCaptureStates(serverLevel); + try { + if (!placedFeature.place( + populator, + generator, + serverLevel.random, + new BlockPos(pt.x(), pt.y(), pt.z()) + )) { + return null; + } + List placedBlocks = new ArrayList<>(populator.getList()); + placedBlocks.addAll(serverLevel.capturedBlockStates.values()); + return placedBlocks; + } finally { + postCaptureBlockStates(serverLevel); + } + }); + + return placeFeatureIntoSession(session, populator, placed); + } + private boolean placeFeatureIntoSession( final EditSession editSession, final FaweBlockStateListPopulator populator, diff --git a/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightPlacementStateProcessor.java b/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightPlacementStateProcessor.java index 1cee9692bf..5619bd5b41 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightPlacementStateProcessor.java +++ b/worldedit-bukkit/adapters/adapter-1_21_4/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_4/PaperweightPlacementStateProcessor.java @@ -35,11 +35,7 @@ public class PaperweightPlacementStateProcessor extends PlacementStateProcessor public PaperweightPlacementStateProcessor(Extent extent, BlockTypeMask mask, Region region) { super(extent, mask, region); - World world = ExtentTraverser.getWorldFromExtent(extent); - if (world == null) { - throw new UnsupportedOperationException( - "World is required for PlacementStateProcessor but none found in given extent."); - } + World world = getWorldFromExtent(extent); BukkitWorld bukkitWorld; if (world instanceof WorldWrapper wrapper) { bukkitWorld = (BukkitWorld) wrapper.getParent(); diff --git a/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_5/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_5/PaperweightAdapter.java index 7dfb944b6c..0d95e3fa40 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_5/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_5/PaperweightAdapter.java @@ -66,6 +66,7 @@ import com.sk89q.worldedit.world.entity.EntityTypes; import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; import com.sk89q.worldedit.world.generation.StructureType; +import com.sk89q.worldedit.world.generation.TreeType; import com.sk89q.worldedit.world.item.ItemType; import net.minecraft.SharedConstants; import net.minecraft.Util; @@ -126,6 +127,9 @@ import net.minecraft.world.level.dimension.LevelStem; import net.minecraft.world.level.levelgen.WorldOptions; import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.feature.FallenTreeFeature; +import net.minecraft.world.level.levelgen.feature.TreeFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -891,6 +895,19 @@ public void initializeRegistries() { } } + // Trees + Registry placedFeatureRegistry = server.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE); + for (ResourceLocation name : placedFeatureRegistry.keySet()) { + // Do some hackery to make sure this is a tree + var underlyingFeature = placedFeatureRegistry.get(name).get().value().feature().value().feature(); + if (underlyingFeature instanceof TreeFeature || underlyingFeature instanceof FallenTreeFeature) { + String key = name.toString(); + if (TreeType.REGISTRY.get(key) == null) { + TreeType.REGISTRY.register(key, new TreeType(key)); + } + } + } + // BiomeCategories Registry biomeRegistry = server.registryAccess().lookupOrThrow(Registries.BIOME); biomeRegistry.getTags().forEach(tag -> { @@ -909,6 +926,17 @@ public void initializeRegistries() { }); } + @Override + public boolean generateTree(TreeType treeType, World world, EditSession session, BlockVector3 pt) throws MaxChangedBlocksException { + ServerLevel originalWorld = ((CraftWorld) world).getHandle(); + PlacedFeature feature = originalWorld.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE).getValue(ResourceLocation.tryParse(treeType.id())); + ServerChunkCache chunkManager = originalWorld.getChunkSource(); + try (PaperweightServerLevelDelegateProxy.LevelAndProxy proxyLevel = + PaperweightServerLevelDelegateProxy.newInstance(session, originalWorld, this)) { + return feature != null && feature.place(proxyLevel.level(), chunkManager.getGenerator(), random, new BlockPos(pt.x(), pt.y(), pt.z())); + } + } + public boolean generateFeature(ConfiguredFeatureType type, World world, EditSession session, BlockVector3 pt) { ServerLevel originalWorld = ((CraftWorld) world).getHandle(); ConfiguredFeature feature = originalWorld.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).getValue(ResourceLocation.tryParse(type.id())); diff --git a/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_5/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_5/PaperweightFaweAdapter.java index 69ba2f39d6..715741dbb7 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_5/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_5/PaperweightFaweAdapter.java @@ -20,6 +20,7 @@ import com.mojang.serialization.Codec; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.WorldEditPlugin; @@ -53,6 +54,7 @@ import com.sk89q.worldedit.world.entity.EntityType; import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; import com.sk89q.worldedit.world.generation.StructureType; +import com.sk89q.worldedit.world.generation.TreeType; import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BlockMaterial; import io.papermc.lib.PaperLib; @@ -93,6 +95,7 @@ import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -682,6 +685,44 @@ public boolean generateStructure(StructureType type, World world, EditSession ed return placeFeatureIntoSession(editSession, populator, placed); } + @Override + public boolean generateTree( + final TreeType treeType, + final World world, + final EditSession session, + final BlockVector3 pt + ) throws MaxChangedBlocksException { + ServerLevel serverLevel = getServerLevel(world); + ChunkGenerator generator = serverLevel.getMinecraftWorld().getChunkSource().getGenerator(); + + PlacedFeature placedFeature = serverLevel + .registryAccess() + .lookupOrThrow(Registries.PLACED_FEATURE) + .getValue(ResourceLocation.tryParse(treeType.id())); + + FaweBlockStateListPopulator populator = new FaweBlockStateListPopulator(serverLevel); + List placed = TaskManager.taskManager().sync(() -> { + preCaptureStates(serverLevel); + try { + if (!placedFeature.place( + populator, + generator, + serverLevel.random, + new BlockPos(pt.x(), pt.y(), pt.z()) + )) { + return null; + } + List placedBlocks = new ArrayList<>(populator.getSnapshotBlocks()); + placedBlocks.addAll(serverLevel.capturedBlockStates.values()); + return placedBlocks; + } finally { + postCaptureBlockStates(serverLevel); + } + }); + + return placeFeatureIntoSession(session, populator, placed); + } + private boolean placeFeatureIntoSession( final EditSession editSession, final FaweBlockStateListPopulator populator, diff --git a/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_5/PaperweightPlacementStateProcessor.java b/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_5/PaperweightPlacementStateProcessor.java index 23d649a456..898559a3b0 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_5/PaperweightPlacementStateProcessor.java +++ b/worldedit-bukkit/adapters/adapter-1_21_5/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_5/PaperweightPlacementStateProcessor.java @@ -35,11 +35,7 @@ public class PaperweightPlacementStateProcessor extends PlacementStateProcessor public PaperweightPlacementStateProcessor(Extent extent, BlockTypeMask mask, Region region) { super(extent, mask, region); - World world = ExtentTraverser.getWorldFromExtent(extent); - if (world == null) { - throw new UnsupportedOperationException( - "World is required for PlacementStateProcessor but none found in given extent."); - } + World world = getWorldFromExtent(extent); BukkitWorld bukkitWorld; if (world instanceof WorldWrapper wrapper) { bukkitWorld = (BukkitWorld) wrapper.getParent(); diff --git a/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_6/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_6/PaperweightAdapter.java index 20acb6a704..8b8529b8f5 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_6/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_6/PaperweightAdapter.java @@ -69,6 +69,7 @@ import com.sk89q.worldedit.world.entity.EntityTypes; import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; import com.sk89q.worldedit.world.generation.StructureType; +import com.sk89q.worldedit.world.generation.TreeType; import com.sk89q.worldedit.world.item.ItemType; import net.minecraft.SharedConstants; import net.minecraft.Util; @@ -130,6 +131,10 @@ import net.minecraft.world.level.dimension.LevelStem; import net.minecraft.world.level.levelgen.WorldOptions; import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.feature.CoralTreeFeature; +import net.minecraft.world.level.levelgen.feature.FallenTreeFeature; +import net.minecraft.world.level.levelgen.feature.TreeFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -226,6 +231,9 @@ public PaperweightAdapter() throws NoSuchFieldException, NoSuchMethodException { if (dataVersion != Constants.DATA_VERSION_MC_1_21_6 && dataVersion != Constants.DATA_VERSION_MC_1_21_7 && dataVersion != Constants.DATA_VERSION_MC_1_21_8) { throw new UnsupportedClassVersionError("Not 1.21.(6/7/8)!"); } + if (dataVersion >= Constants.DATA_VERSION_MC_26_1) { + throw new RuntimeException("Force prevent this loading on 26.1+"); + } serverWorldsField = CraftServer.class.getDeclaredField("worlds"); serverWorldsField.setAccessible(true); @@ -914,6 +922,19 @@ public void initializeRegistries() { } } + // Trees + Registry placedFeatureRegistry = server.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE); + for (ResourceLocation name : placedFeatureRegistry.keySet()) { + // Do some hackery to make sure this is a tree + var underlyingFeature = placedFeatureRegistry.get(name).get().value().feature().value().feature(); + if (underlyingFeature instanceof TreeFeature || underlyingFeature instanceof FallenTreeFeature || underlyingFeature instanceof CoralTreeFeature) { + String key = name.toString(); + if (TreeType.REGISTRY.get(key) == null) { + TreeType.REGISTRY.register(key, new TreeType(key)); + } + } + } + // BiomeCategories Registry biomeRegistry = server.registryAccess().lookupOrThrow(Registries.BIOME); biomeRegistry.getTags().forEach(tag -> { @@ -932,6 +953,17 @@ public void initializeRegistries() { }); } + @Override + public boolean generateTree(TreeType treeType, World world, EditSession session, BlockVector3 pt) throws MaxChangedBlocksException { + ServerLevel originalWorld = ((CraftWorld) world).getHandle(); + PlacedFeature feature = originalWorld.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE).getValue(ResourceLocation.tryParse(treeType.id())); + ServerChunkCache chunkManager = originalWorld.getChunkSource(); + try (PaperweightServerLevelDelegateProxy.LevelAndProxy proxyLevel = + PaperweightServerLevelDelegateProxy.newInstance(session, originalWorld, this)) { + return feature != null && feature.place(proxyLevel.level(), chunkManager.getGenerator(), random, new BlockPos(pt.x(), pt.y(), pt.z())); + } + } + public boolean generateFeature(ConfiguredFeatureType type, World world, EditSession session, BlockVector3 pt) { ServerLevel originalWorld = ((CraftWorld) world).getHandle(); ConfiguredFeature feature = originalWorld.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).getValue(ResourceLocation.tryParse(type.id())); diff --git a/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_6/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_6/PaperweightFaweAdapter.java index 7c6c8d5095..86071c5ac5 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_6/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_6/PaperweightFaweAdapter.java @@ -20,6 +20,7 @@ import com.mojang.serialization.Codec; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.WorldEditPlugin; @@ -53,6 +54,7 @@ import com.sk89q.worldedit.world.entity.EntityType; import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; import com.sk89q.worldedit.world.generation.StructureType; +import com.sk89q.worldedit.world.generation.TreeType; import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BlockMaterial; import io.papermc.lib.PaperLib; @@ -93,6 +95,7 @@ import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -698,6 +701,44 @@ public boolean canTransformBlocks() { return placeFeatureIntoSession(editSession, populator, placed); } + @Override + public boolean generateTree( + final TreeType treeType, + final World world, + final EditSession session, + final BlockVector3 pt + ) throws MaxChangedBlocksException { + ServerLevel serverLevel = getServerLevel(world); + ChunkGenerator generator = serverLevel.getMinecraftWorld().getChunkSource().getGenerator(); + + PlacedFeature placedFeature = serverLevel + .registryAccess() + .lookupOrThrow(Registries.PLACED_FEATURE) + .getValue(ResourceLocation.tryParse(treeType.id())); + + FaweBlockStateListPopulator populator = new FaweBlockStateListPopulator(serverLevel); + List placed = TaskManager.taskManager().sync(() -> { + preCaptureStates(serverLevel); + try { + if (!placedFeature.place( + populator, + generator, + serverLevel.random, + new BlockPos(pt.x(), pt.y(), pt.z()) + )) { + return null; + } + List placedBlocks = new ArrayList<>(populator.getSnapshotBlocks()); + placedBlocks.addAll(serverLevel.capturedBlockStates.values()); + return placedBlocks; + } finally { + postCaptureBlockStates(serverLevel); + } + }); + + return placeFeatureIntoSession(session, populator, placed); + } + private boolean placeFeatureIntoSession( final EditSession editSession, final FaweBlockStateListPopulator populator, diff --git a/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_6/PaperweightPlacementStateProcessor.java b/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_6/PaperweightPlacementStateProcessor.java index 6d83af0ba0..9d3b1dd60d 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_6/PaperweightPlacementStateProcessor.java +++ b/worldedit-bukkit/adapters/adapter-1_21_6/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_6/PaperweightPlacementStateProcessor.java @@ -35,11 +35,7 @@ public class PaperweightPlacementStateProcessor extends PlacementStateProcessor public PaperweightPlacementStateProcessor(Extent extent, BlockTypeMask mask, Region region) { super(extent, mask, region); - World world = ExtentTraverser.getWorldFromExtent(extent); - if (world == null) { - throw new UnsupportedOperationException( - "World is required for PlacementStateProcessor but none found in given extent."); - } + World world = getWorldFromExtent(extent); BukkitWorld bukkitWorld; if (world instanceof WorldWrapper wrapper) { bukkitWorld = (BukkitWorld) wrapper.getParent(); diff --git a/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_9/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_9/PaperweightAdapter.java index 17898f34f4..78b86d485b 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_9/PaperweightAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/ext/fawe/v1_21_9/PaperweightAdapter.java @@ -69,6 +69,7 @@ import com.sk89q.worldedit.world.entity.EntityTypes; import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; import com.sk89q.worldedit.world.generation.StructureType; +import com.sk89q.worldedit.world.generation.TreeType; import com.sk89q.worldedit.world.item.ItemType; import net.minecraft.SharedConstants; import net.minecraft.Util; @@ -129,6 +130,10 @@ import net.minecraft.world.level.dimension.LevelStem; import net.minecraft.world.level.levelgen.WorldOptions; import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.feature.CoralTreeFeature; +import net.minecraft.world.level.levelgen.feature.FallenTreeFeature; +import net.minecraft.world.level.levelgen.feature.TreeFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -911,6 +916,19 @@ public void initializeRegistries() { } } + // Trees + Registry placedFeatureRegistry = server.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE); + for (ResourceLocation name : placedFeatureRegistry.keySet()) { + // Do some hackery to make sure this is a tree + var underlyingFeature = placedFeatureRegistry.get(name).get().value().feature().value().feature(); + if (underlyingFeature instanceof TreeFeature || underlyingFeature instanceof FallenTreeFeature || underlyingFeature instanceof CoralTreeFeature) { + String key = name.toString(); + if (TreeType.REGISTRY.get(key) == null) { + TreeType.REGISTRY.register(key, new TreeType(key)); + } + } + } + // BiomeCategories Registry biomeRegistry = server.registryAccess().lookupOrThrow(Registries.BIOME); biomeRegistry.getTags().forEach(tag -> { @@ -929,6 +947,17 @@ public void initializeRegistries() { }); } + @Override + public boolean generateTree(TreeType treeType, World world, EditSession session, BlockVector3 pt) throws MaxChangedBlocksException { + ServerLevel originalWorld = ((CraftWorld) world).getHandle(); + PlacedFeature feature = originalWorld.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE).getValue(ResourceLocation.tryParse(treeType.id())); + ServerChunkCache chunkManager = originalWorld.getChunkSource(); + try (PaperweightServerLevelDelegateProxy.LevelAndProxy proxyLevel = + PaperweightServerLevelDelegateProxy.newInstance(session, originalWorld, this)) { + return feature != null && feature.place(proxyLevel.level(), chunkManager.getGenerator(), random, new BlockPos(pt.x(), pt.y(), pt.z())); + } + } + public boolean generateFeature(ConfiguredFeatureType type, World world, EditSession session, BlockVector3 pt) { ServerLevel originalWorld = ((CraftWorld) world).getHandle(); ConfiguredFeature feature = originalWorld.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).getValue(ResourceLocation.tryParse(type.id())); diff --git a/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_9/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_9/PaperweightFaweAdapter.java index a14cd98492..9cc900128f 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_9/PaperweightFaweAdapter.java +++ b/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_9/PaperweightFaweAdapter.java @@ -20,6 +20,7 @@ import com.mojang.serialization.Codec; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.WorldEditPlugin; @@ -53,6 +54,7 @@ import com.sk89q.worldedit.world.entity.EntityType; import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; import com.sk89q.worldedit.world.generation.StructureType; +import com.sk89q.worldedit.world.generation.TreeType; import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BlockMaterial; import io.papermc.lib.PaperLib; @@ -93,6 +95,7 @@ import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.Structure; import net.minecraft.world.level.levelgen.structure.StructureStart; @@ -693,6 +696,44 @@ public boolean generateStructure(StructureType type, World world, EditSession ed return placeFeatureIntoSession(editSession, populator, placed); } + @Override + public boolean generateTree( + final TreeType treeType, + final World world, + final EditSession session, + final BlockVector3 pt + ) throws MaxChangedBlocksException { + ServerLevel serverLevel = getServerLevel(world); + ChunkGenerator generator = serverLevel.getMinecraftWorld().getChunkSource().getGenerator(); + + PlacedFeature placedFeature = serverLevel + .registryAccess() + .lookupOrThrow(Registries.PLACED_FEATURE) + .getValue(ResourceLocation.tryParse(treeType.id())); + + FaweBlockStateListPopulator populator = new FaweBlockStateListPopulator(serverLevel); + List placed = TaskManager.taskManager().sync(() -> { + preCaptureStates(serverLevel); + try { + if (!placedFeature.place( + populator, + generator, + serverLevel.random, + new BlockPos(pt.x(), pt.y(), pt.z()) + )) { + return null; + } + List placedBlocks = new ArrayList<>(populator.getSnapshotBlocks()); + placedBlocks.addAll(serverLevel.capturedBlockStates.values()); + return placedBlocks; + } finally { + postCaptureBlockStates(serverLevel); + } + }); + + return placeFeatureIntoSession(session, populator, placed); + } + private boolean placeFeatureIntoSession( final EditSession editSession, final FaweBlockStateListPopulator populator, diff --git a/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_9/PaperweightPlacementStateProcessor.java b/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_9/PaperweightPlacementStateProcessor.java index ff656a5f2a..daae5de4c3 100644 --- a/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_9/PaperweightPlacementStateProcessor.java +++ b/worldedit-bukkit/adapters/adapter-1_21_9/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v1_21_9/PaperweightPlacementStateProcessor.java @@ -35,11 +35,7 @@ public class PaperweightPlacementStateProcessor extends PlacementStateProcessor public PaperweightPlacementStateProcessor(Extent extent, BlockTypeMask mask, Region region) { super(extent, mask, region); - World world = ExtentTraverser.getWorldFromExtent(extent); - if (world == null) { - throw new UnsupportedOperationException( - "World is required for PlacementStateProcessor but none found in given extent."); - } + World world = getWorldFromExtent(extent); BukkitWorld bukkitWorld; if (world instanceof WorldWrapper wrapper) { bukkitWorld = (BukkitWorld) wrapper.getParent(); diff --git a/worldedit-bukkit/adapters/adapter-26.1/build.gradle.kts b/worldedit-bukkit/adapters/adapter-26.1/build.gradle.kts new file mode 100644 index 0000000000..e5d0463352 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-26.1/build.gradle.kts @@ -0,0 +1,11 @@ +import io.papermc.paperweight.userdev.PaperweightUserDependenciesExtension + +plugins { + id("buildlogic.adapter") +} + +dependencies { + // https://artifactory.papermc.io/ui/native/universe/io/papermc/paper/dev-bundle/ + the().paperDevBundle("26.1.2.build.+") + compileOnly(libs.paperLib) +} diff --git a/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/FaweBlockStateListPopulator.java b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/FaweBlockStateListPopulator.java new file mode 100644 index 0000000000..2b23a3356e --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/FaweBlockStateListPopulator.java @@ -0,0 +1,231 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1; + +import com.fastasyncworldedit.core.util.MathMan; +import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1.PaperweightChunkAccessProxy; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Holder; +import net.minecraft.core.SectionPos; +import net.minecraft.core.particles.ParticleOptions; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.sounds.SoundSource; +import net.minecraft.util.RandomSource; +import net.minecraft.world.DifficultyInstance; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.flag.FeatureFlagSet; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeManager; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.border.WorldBorder; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.ChunkSource; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.lighting.LevelLightEngine; +import net.minecraft.world.level.material.FluidState; +import org.bukkit.craftbukkit.util.BlockStateListPopulator; +import org.jetbrains.annotations.NotNull; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; + +public class FaweBlockStateListPopulator extends BlockStateListPopulator { + + private final Long2ObjectOpenHashMap chunkProxies = new Long2ObjectOpenHashMap<>(); + private final ServerLevel world; + + public FaweBlockStateListPopulator(ServerLevel world) { + super(world); + this.world = world; + } + + @Override + public long getSeed() { + return world.getSeed(); + } + + @Override + @Nonnull + public ServerLevel getLevel() { + return world.getLevel(); + } + + @Override + @Nonnull + public DifficultyInstance getCurrentDifficultyAt(final BlockPos pos) { + return world.getCurrentDifficultyAt(pos); + } + + @Override + public MinecraftServer getServer() { + return world.getServer(); + } + + @Override + @Nonnull + public ChunkSource getChunkSource() { + return world.getChunkSource(); + } + + @Override + @Nonnull + public RandomSource getRandom() { + return world.getRandom(); + } + + @Override + public void playSound( + final Entity source, + final BlockPos pos, + final SoundEvent sound, + final SoundSource category, + final float volume, + final float pitch + ) { + // don't for now + } + + @Override + public void addParticle( + final ParticleOptions parameters, + final double x, + final double y, + final double z, + final double velocityX, + final double velocityY, + final double velocityZ + ) { + // definitely don't + } + + @Override + public @NotNull List players() { + return world.players(); + } + + @Override + public ChunkAccess getChunk(final int chunkX, final int chunkZ, final ChunkStatus leastStatus, final boolean create) { + ChunkAccess worldChunk = world.getChunk(chunkX, chunkZ, leastStatus, create); + PaperweightChunkAccessProxy proxy = chunkProxies.compute( + MathMan.pairInt(chunkX, chunkZ), + (k, v) -> v == null ? PaperweightChunkAccessProxy.getInstance() : v + ); + proxy.parent = worldChunk; + return proxy; + } + + @Override + @Nonnull + public BiomeManager getBiomeManager() { + return world.getBiomeManager(); + } + + @Override + @Nonnull + public Holder getUncachedNoiseBiome(final int biomeX, final int biomeY, final int biomeZ) { + return world.getUncachedNoiseBiome(biomeX, biomeY, biomeZ); + } + + @Override + public int getSeaLevel() { + return world.getSeaLevel(); + } + + @Override + public @Nonnull ChunkAccess getChunk(final @Nonnull BlockPos pos) { + ChunkAccess worldChunk = world.getChunk(pos); + PaperweightChunkAccessProxy proxy = chunkProxies.compute( + MathMan.pairInt(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ())), + (k, v) -> v == null ? PaperweightChunkAccessProxy.getInstance() : v + ); + proxy.parent = worldChunk; + return proxy; + } + + @Override + public @Nonnull ChunkAccess getChunk(final int chunkX, final int chunkZ) { + ChunkAccess worldChunk = world.getChunk(chunkX, chunkZ); + PaperweightChunkAccessProxy proxy = chunkProxies.compute( + MathMan.pairInt(chunkX, chunkZ), + (k, v) -> v == null ? PaperweightChunkAccessProxy.getInstance() : v + ); + proxy.parent = worldChunk; + return proxy; + } + + @Override + public @Nonnull ChunkAccess getChunk(final int chunkX, final int chunkZ, final @Nonnull ChunkStatus chunkStatus) { + ChunkAccess worldChunk = world.getChunk(chunkX, chunkZ, chunkStatus); + PaperweightChunkAccessProxy proxy = chunkProxies.compute( + MathMan.pairInt(chunkX, chunkZ), + (k, v) -> v == null ? PaperweightChunkAccessProxy.getInstance() : v + ); + proxy.parent = worldChunk; + return proxy; + } + + @Override + @Nonnull + public FeatureFlagSet enabledFeatures() { + return world.enabledFeatures(); + } + + @Override + @Nonnull + public LevelLightEngine getLightEngine() { + return world.getLightEngine(); + } + + @Nullable + @Override + public ChunkAccess getChunkIfLoadedImmediately(final int x, final int z) { + return world.getChunkIfLoadedImmediately(x, z); + } + + @Override + public BlockState getBlockStateIfLoaded(final BlockPos blockposition) { + return world.getBlockStateIfLoaded(blockposition); + } + + @Override + public FluidState getFluidIfLoaded(final BlockPos blockposition) { + return world.getFluidIfLoaded(blockposition); + } + + @Override + @Nonnull + public WorldBorder getWorldBorder() { + return world.getWorldBorder(); + } + + @Override + public boolean setBlock(final BlockPos pos, final BlockState state, final int flags, final int maxUpdateDepth) { + return world.setBlock(pos, state, flags, maxUpdateDepth); + } + + @Override + public boolean removeBlock(final BlockPos pos, final boolean move) { + return world.removeBlock(pos, move); + } + + @Override + public boolean destroyBlock(final BlockPos pos, final boolean drop, final Entity breakingEntity, final int maxUpdateDepth) { + return world.destroyBlock(pos, drop, breakingEntity, maxUpdateDepth); + } + + @Override + @Nonnull + public BlockState getBlockState(final BlockPos pos) { + return world.getBlockState(pos); + } + + @Override + public boolean setBlock(final BlockPos pos, final BlockState state, final int flags) { + return world.setBlock(pos, state, flags); + } + +} diff --git a/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/LinOps.java b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/LinOps.java new file mode 100644 index 0000000000..c2d472cebc --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/LinOps.java @@ -0,0 +1,659 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1; + +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.MapLike; +import com.mojang.serialization.RecordBuilder; +import it.unimi.dsi.fastutil.bytes.ByteArrayList; +import it.unimi.dsi.fastutil.bytes.ByteList; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; +import it.unimi.dsi.fastutil.longs.LongArrayList; +import it.unimi.dsi.fastutil.longs.LongList; +import org.enginehub.linbus.common.LinTagId; +import org.enginehub.linbus.tree.LinByteArrayTag; +import org.enginehub.linbus.tree.LinByteTag; +import org.enginehub.linbus.tree.LinCompoundTag; +import org.enginehub.linbus.tree.LinDoubleTag; +import org.enginehub.linbus.tree.LinEndTag; +import org.enginehub.linbus.tree.LinFloatTag; +import org.enginehub.linbus.tree.LinIntArrayTag; +import org.enginehub.linbus.tree.LinIntTag; +import org.enginehub.linbus.tree.LinListTag; +import org.enginehub.linbus.tree.LinLongArrayTag; +import org.enginehub.linbus.tree.LinLongTag; +import org.enginehub.linbus.tree.LinNumberTag; +import org.enginehub.linbus.tree.LinShortTag; +import org.enginehub.linbus.tree.LinStringTag; +import org.enginehub.linbus.tree.LinTag; +import org.enginehub.linbus.tree.LinTagType; +import org.jetbrains.annotations.Nullable; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.Spliterators; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +/** + * DynamicOps implementation for direct LinBus interaction. + * Basically a copy of {@link net.minecraft.nbt.NbtOps}, but for Lin (with changes so it actually works). + */ +public class LinOps implements DynamicOps> { + + static final DynamicOps> INSTANCE = new LinOps(); + + private LinOps() { + } + + @Override + public LinTag empty() { + return LinEndTag.instance(); + } + + @Override + public LinTag emptyList() { + return LinListTag.empty(LinTagType.endTag()); + } + + @Override + public LinTag emptyMap() { + return LinCompoundTag.builder().build(); + } + + @Override + public U convertTo(final DynamicOps outOps, final LinTag input) { + return switch (input) { + case LinEndTag ignored -> outOps.empty(); + case LinByteTag tag -> outOps.createByte(tag.valueAsByte()); + case LinShortTag tag -> outOps.createShort(tag.valueAsShort()); + case LinIntTag tag -> outOps.createInt(tag.valueAsInt()); + case LinLongTag tag -> outOps.createLong(tag.valueAsLong()); + case LinFloatTag tag -> outOps.createFloat(tag.valueAsFloat()); + case LinDoubleTag tag -> outOps.createDouble(tag.valueAsDouble()); + case LinByteArrayTag tag -> outOps.createByteList(ByteBuffer.wrap(tag.value())); + case LinStringTag tag -> outOps.createString(tag.value()); + case LinListTag tag -> convertList(outOps, tag); + case LinCompoundTag tag -> convertMap(outOps, tag); + case LinIntArrayTag tag -> outOps.createIntList(Arrays.stream(tag.value())); + case LinLongArrayTag tag -> outOps.createLongList(Arrays.stream(tag.value())); + }; + } + + @Override + public DataResult getNumberValue(final LinTag input) { + if (input instanceof LinNumberTag tag) { + return DataResult.success(tag.value()); + } + return DataResult.error(() -> "Not a number"); + } + + @Override + public LinTag createNumeric(final Number i) { + return LinDoubleTag.of(i.doubleValue()); + } + + @Override + public LinTag createByte(final byte value) { + return LinByteTag.of(value); + } + + @Override + public LinTag createShort(final short value) { + return LinShortTag.of(value); + } + + @Override + public LinTag createInt(final int value) { + return LinIntTag.of(value); + } + + @Override + public LinTag createLong(final long value) { + return LinLongTag.of(value); + } + + @Override + public LinTag createFloat(final float value) { + return LinFloatTag.of(value); + } + + @Override + public LinTag createDouble(final double value) { + return LinDoubleTag.of(value); + } + + @Override + public LinTag createBoolean(final boolean value) { + return LinByteTag.of(value ? (byte) 1 : (byte) 0); + } + + @Override + public DataResult getStringValue(final LinTag input) { + if (input instanceof LinStringTag tag) { + return DataResult.success(tag.value()); + } + return DataResult.error(() -> "Not a string"); + } + + @Override + public LinTag createString(final String value) { + return LinStringTag.of(value); + } + + @Override + public DataResult> mergeToList(final LinTag list, final LinTag value) { + return createCollector(list) + .>>map(collector -> DataResult.success(collector.accept(value).result())) + .orElseGet(() -> DataResult.error(() -> "mergeToList called with not a list: " + list, list)); + } + + @Override + public DataResult> mergeToList(final LinTag list, final List> values) { + return createCollector(list) + .>>map(collector -> DataResult.success(collector.acceptAll(values).result())) + .orElseGet(() -> DataResult.error(() -> "mergeToList called with not a list: " + list, list)); + } + + @Override + public DataResult> mergeToMap(final LinTag map, final LinTag key, final LinTag value) { + if (!(map instanceof LinCompoundTag) && !(map instanceof LinEndTag)) { + return DataResult.error(() -> "mergeToMap called with not a map: " + map, map); + } + if (!(key instanceof LinStringTag keyStringTag)) { + return DataResult.error(() -> "key is not a string: " + key, map); + } + if (map instanceof LinCompoundTag mapCompoundTag) { + if (value == empty()) { + return DataResult.success(mapCompoundTag); + } + return DataResult.success(mapCompoundTag.toBuilder().put(keyStringTag.value(), value).build()); + } + if (value == empty()) { + return DataResult.success(emptyMap()); + } + return DataResult.success(LinCompoundTag.builder().put(keyStringTag.value(), value).build()); + } + + @Override + public DataResult> mergeToMap(final LinTag map, final MapLike> values) { + if (!(map instanceof LinCompoundTag) && !(map instanceof LinEndTag)) { + return DataResult.error(() -> "mergeToMap called with not a map: " + map, map); + } + final Iterator, LinTag>> iterator = values.entries().iterator(); + if (!iterator.hasNext()) { + // if map is LinEndTag (returned by empty()) return a functional empty map + return map == empty() ? DataResult.success(this.emptyMap()) : DataResult.success(map); + } + LinCompoundTag.Builder resultBuilder = map instanceof LinCompoundTag compoundTag ? + compoundTag.toBuilder() : LinCompoundTag.builder(); + List> nonStringKeys = new ArrayList<>(); + iterator.forEachRemaining(entry -> { + LinTag key = entry.getFirst(); + if (key instanceof LinStringTag keyStringTag) { + if (entry.getSecond() != empty()) { + resultBuilder.put(keyStringTag.value(), entry.getSecond()); + } + } else { + nonStringKeys.add(key); + } + }); + return nonStringKeys.isEmpty() ? DataResult.success(resultBuilder.build()) : + DataResult.error(() -> "some keys are not strings: " + nonStringKeys); + } + + @Override + public DataResult> mergeToMap(final LinTag map, final Map, LinTag> values) { + if (!(map instanceof LinCompoundTag) && !(map instanceof LinEndTag)) { + return DataResult.error(() -> "mergeToMap called with not a map: " + map, map); + } + if (values.isEmpty()) { + return map == empty() ? DataResult.success(this.emptyMap()) : DataResult.success(map); + } + LinCompoundTag.Builder resultBuilder = map instanceof LinCompoundTag compoundTag ? + compoundTag.toBuilder() : LinCompoundTag.builder(); + List> nonStringKeys = new ArrayList<>(); + values.forEach((key, value) -> { + if (key instanceof LinStringTag keyStringTag) { + if (value != empty()) { + resultBuilder.put(keyStringTag.value(), value); + } + } else { + nonStringKeys.add(key); + } + }); + return nonStringKeys.isEmpty() ? DataResult.success(resultBuilder.build()) : + DataResult.error(() -> "some keys are not strings: " + nonStringKeys); + } + + @Override + public DataResult, LinTag>>> getMapValues(final LinTag input) { + if (input instanceof LinCompoundTag tag) { + Map> value = tag.value(); + return DataResult.success(value + .entrySet() + .stream() + .map(entry -> Pair.of(this.createString(entry.getKey()), entry.getValue()))); + } + return DataResult.error(() -> "Not a map: " + input); + } + + @Override + public DataResult, LinTag>>> getMapEntries(final LinTag input) { + if (input instanceof LinCompoundTag tag) { + return DataResult.success(consumer -> + tag.value().forEach((s, linTag) -> consumer.accept(this.createString(s), linTag))); + } + return DataResult.error(() -> "Not a map: " + input); + } + + @Override + public DataResult>> getMap(final LinTag input) { + if (input instanceof LinCompoundTag map) { + Map> value = map.value(); + return DataResult.success(new MapLike<>() { + @Override + public @Nullable LinTag get(final LinTag key) { + if (key instanceof LinStringTag tag) { + return value.get(tag.value()); + } + throw new UnsupportedOperationException("Cannot get map entry with non-string key: " + key); + } + + @Override + public @Nullable LinTag get(final String key) { + return value.get(key); + } + + @Override + public Stream, LinTag>> entries() { + return value.entrySet().stream().map(entry -> Pair.of( + LinOps.this.createString(entry.getKey()), + entry.getValue() + )); + } + + @Override + public String toString() { + return "MapLike[" + map + "]"; + } + }); + } + return DataResult.error(() -> "Not a map: " + input); + } + + @Override + public LinTag createMap(final Stream, LinTag>> map) { + LinCompoundTag.Builder builder = LinCompoundTag.builder(); + map.filter(pair -> pair.getSecond() != empty()).forEach(pair -> { + if (pair.getFirst() instanceof LinStringTag key) { + builder.put(key.value(), pair.getSecond()); + return; + } + throw new UnsupportedOperationException("Cannot create map entry with non-string key: " + pair.getFirst()); + }); + return builder.build(); + } + + @Override + public DataResult>> getStream(final LinTag input) { + return switch (input) { + case LinListTag tag -> DataResult.success(StreamSupport.stream( + Spliterators.spliterator( + new Iterator<>() { + private int index; + + @Override + public boolean hasNext() { + return this.index < tag.value().size(); + } + + @Override + public LinTag next() { + if (!this.hasNext()) { + throw new NoSuchElementException(); + } + return tag.get(index++); + } + }, tag.value().size(), 0 + ), false + )); + case LinByteArrayTag tag -> DataResult.success(StreamSupport.stream( + Spliterators.spliterator( + new Iterator() { + private int index; + + @Override + public boolean hasNext() { + return this.index < tag.value().length; + } + + @Override + public LinByteTag next() { + if (!this.hasNext()) { + throw new NoSuchElementException(); + } + return LinByteTag.of(tag.value()[index++]); + } + }, tag.value().length, 0 + ), false + )); + case LinIntArrayTag tag -> DataResult.success( + StreamSupport.stream(Spliterators.spliterator(tag.value(), 0), false) + .map(LinIntTag::of) + ); + case LinLongArrayTag tag -> DataResult.success( + StreamSupport.stream(Spliterators.spliterator(tag.value(), 0), false) + .map(LinLongTag::of) + ); + default -> DataResult.error(() -> "Not a list"); + }; + } + + @Override + public DataResult>>> getList(final LinTag input) { + if (input instanceof LinListTag tag) { + return DataResult.success(consumer -> tag.value().forEach(entry -> { + // Handling of mixed lists: NbtOps doesn't need to do that here given the net.minecraft.nbt.ListTag unwraps the + // entries on insertion. LinListTag doesn't allow mixed list content, so we unwrap on read. + // the only location this is used currently should be components (i.e. sign text) + if (entry instanceof LinCompoundTag compoundTag && compoundTag.value().size() == 1 && compoundTag.value().containsKey("")) { + consumer.accept(compoundTag.value().get("")); + return; + } + consumer.accept(entry); + })); + } + if (input instanceof LinByteArrayTag tag) { + return DataResult.success(consumer -> { + for (final byte b : tag.value()) { + consumer.accept(this.createByte(b)); + } + }); + } + if (input instanceof LinIntArrayTag tag) { + return DataResult.success(consumer -> { + for (final int i : tag.value()) { + consumer.accept(this.createInt(i)); + } + }); + } + if (input instanceof LinLongArrayTag tag) { + return DataResult.success(consumer -> { + for (final long l : tag.value()) { + consumer.accept(this.createLong(l)); + } + }); + } + return DataResult.error(() -> "Not a list: " + input); + } + + @Override + public DataResult getByteBuffer(final LinTag input) { + if (input instanceof LinByteArrayTag tag) { + return DataResult.success(ByteBuffer.wrap(tag.value())); + } + return DynamicOps.super.getByteBuffer(input); + } + + @Override + public LinTag createByteList(final ByteBuffer input) { + ByteBuffer buffer = input.duplicate().clear(); + byte[] bytes = new byte[buffer.capacity()]; + buffer.get(0, bytes, 0, bytes.length); + return LinByteArrayTag.of(bytes); + } + + @Override + public DataResult getIntStream(final LinTag input) { + if (input instanceof LinIntArrayTag tag) { + return DataResult.success(IntStream.of(tag.value())); + } + return DynamicOps.super.getIntStream(input); + } + + @Override + public LinTag createIntList(final IntStream input) { + return LinIntArrayTag.of(input.toArray()); + } + + @Override + public DataResult getLongStream(final LinTag input) { + if (input instanceof LinLongArrayTag tag) { + return DataResult.success(LongStream.of(tag.value())); + } + return DynamicOps.super.getLongStream(input); + } + + @Override + public LinTag createLongList(final LongStream input) { + return LinLongArrayTag.of(input.toArray()); + } + + @Override + public LinTag createList(final Stream> input) { + return rawTagListToLinList(input.collect(Collectors.toList())); + } + + @Override + public LinTag remove(final LinTag input, final String key) { + if (input instanceof LinCompoundTag tag) { + return tag.toBuilder().remove(key).build(); + } + return input; + } + + @Override + public String toString() { + return "LinOps"; + } + + @Override + public RecordBuilder> mapBuilder() { + return new LinRecordBuilder(); + } + + private static Optional createCollector(LinTag tag) { + return switch (tag) { + case LinEndTag ignored -> Optional.of(new GenericListCollector()); + case LinListTag listTag -> Optional.of(new GenericListCollector(listTag)); + case LinByteArrayTag byteArrayTag -> Optional.of(new ByteListCollector(byteArrayTag)); + case LinIntArrayTag intArrayTag -> Optional.of(new IntListCollector(intArrayTag)); + case LinLongArrayTag longArrayTag -> Optional.of(new LongListCollector(longArrayTag)); + default -> Optional.empty(); + }; + } + + // net.minecraft.nbt.ListTag#identifyRawElementType + private static LinTagId identifyTagTypeOfUncheckedList(List> list) { + LinTagId type = LinTagId.END; + for (final LinTag linTag : list) { + if (type == LinTagId.END) { + type = linTag.type().id(); + continue; + } + if (type != linTag.type().id()) { + return LinTagId.COMPOUND; + } + } + return type; + } + + static LinTag rawTagListToLinList(List> content) { + final LinTagId typeId = identifyTagTypeOfUncheckedList(content); + LinListTag.Builder> builder = LinListTag.builder(LinTagType.fromId(typeId)); + for (final LinTag entry : content) { + if (typeId == LinTagId.COMPOUND && !(entry instanceof LinCompoundTag)) { + builder.add(LinCompoundTag.of(Map.of("", entry))); + continue; + } + builder.add(entry); + } + return builder.build(); + } + + private interface ListCollector { + + ListCollector accept(LinTag tag); + + default ListCollector acceptAll(Iterable> iterable) { + ListCollector collector = this; + for (final LinTag linTag : iterable) { + collector = collector.accept(linTag); + } + return collector; + } + + LinTag result(); + + } + + private static class GenericListCollector implements ListCollector { + + private final List> list = new ArrayList<>(); + + GenericListCollector() { + } + + GenericListCollector(LinListTag listTag) { + this.list.addAll(listTag.value()); + } + + GenericListCollector(ByteList byteList) { + byteList.forEach(value -> list.add(LinByteTag.of(value))); + } + + GenericListCollector(IntList intList) { + intList.forEach(value -> list.add(LinIntTag.of(value))); + } + + GenericListCollector(LongList longList) { + longList.forEach(value -> list.add(LinLongTag.of(value))); + } + + @Override + public ListCollector accept(final LinTag tag) { + this.list.add(tag); + return this; + } + + @Override + public LinTag result() { + return rawTagListToLinList(this.list); + } + + } + + private record ByteListCollector(ByteList byteList) implements ListCollector { + + private ByteListCollector(final LinByteArrayTag byteList) { + this(ByteArrayList.of(byteList.value())); + } + + @Override + public ListCollector accept(final LinTag tag) { + if (tag instanceof LinByteTag byteTag) { + byteList.add(byteTag.valueAsByte()); + return this; + } + return new GenericListCollector(this.byteList).accept(tag); + } + + @Override + public LinTag result() { + return LinByteArrayTag.of(this.byteList.toByteArray()); + } + + } + + private record IntListCollector(IntList intList) implements ListCollector { + + private IntListCollector(final LinIntArrayTag intList) { + this(IntArrayList.of(intList.value())); + } + + @Override + public ListCollector accept(final LinTag tag) { + if (tag instanceof LinIntTag intTag) { + intList.add(intTag.valueAsInt()); + return this; + } + return new GenericListCollector(this.intList).accept(tag); + } + + @Override + public LinTag result() { + return LinIntArrayTag.of(this.intList.toIntArray()); + } + + } + + private record LongListCollector(LongList longList) implements ListCollector { + + private LongListCollector(final LinLongArrayTag longList) { + this(LongArrayList.of(longList.value())); + } + + @Override + public ListCollector accept(final LinTag tag) { + if (tag instanceof LinLongTag longTag) { + longList.add(longTag.valueAsLong()); + return this; + } + return new GenericListCollector(this.longList).accept(tag); + } + + @Override + public LinTag result() { + return LinLongArrayTag.of(this.longList.toLongArray()); + } + + } + + private class LinRecordBuilder extends RecordBuilder.AbstractStringBuilder, LinCompoundTag.Builder> { + + protected LinRecordBuilder() { + super(LinOps.this); + } + + @Override + protected LinCompoundTag.Builder initBuilder() { + return LinCompoundTag.builder(); + } + + @Override + protected LinCompoundTag.Builder append(final String key, final LinTag value, final LinCompoundTag.Builder builder) { + if (value != ops().empty()) { + return builder.put(key, value); + } + return builder; + } + + @Override + protected DataResult> build(final LinCompoundTag.Builder builder, final LinTag prefix) { + if (prefix != null && !prefix.type().equals(LinTagType.endTag())) { + if (!(prefix instanceof LinCompoundTag prefixCompound)) { + return DataResult.error(() -> "mergeToMap called with not a map: " + prefix, prefix); + } + LinCompoundTag.Builder prefixedBuilder = prefixCompound.toBuilder(); + builder.build().value().forEach(prefixedBuilder::put); + return DataResult.success(prefixedBuilder.build()); + } + return DataResult.success(builder.build()); + } + + } + +} diff --git a/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/LinValueInput.java b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/LinValueInput.java new file mode 100644 index 0000000000..721e9d6fd4 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/LinValueInput.java @@ -0,0 +1,477 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1; + +import com.google.common.collect.AbstractIterator; +import com.google.common.collect.Streams; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.MapCodec; +import net.minecraft.core.HolderLookup; +import net.minecraft.util.ProblemReporter; +import net.minecraft.world.level.storage.ValueInput; +import org.enginehub.linbus.tree.LinByteTag; +import org.enginehub.linbus.tree.LinCompoundTag; +import org.enginehub.linbus.tree.LinIntArrayTag; +import org.enginehub.linbus.tree.LinListTag; +import org.enginehub.linbus.tree.LinNumberTag; +import org.enginehub.linbus.tree.LinShortTag; +import org.enginehub.linbus.tree.LinStringTag; +import org.enginehub.linbus.tree.LinTag; +import org.enginehub.linbus.tree.LinTagType; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + +import java.util.Collections; +import java.util.Iterator; +import java.util.ListIterator; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Stream; + +public class LinValueInput implements ValueInput { + + private final ValueInputContext context; + private final ProblemReporter problemReporter; + private final LinCompoundTag input; + + private LinValueInput(ValueInputContext context, ProblemReporter problemReporter, LinCompoundTag input) { + this.context = context; + this.problemReporter = problemReporter; + this.input = input; + } + + public static LinValueInput create(ProblemReporter problemReporter, HolderLookup.Provider registryAccess, LinCompoundTag input) { + return new LinValueInput(new ValueInputContext(registryAccess), problemReporter, input); + } + + @Override + public @NonNull Optional read(final @NonNull String key, final @NonNull Codec codec) { + final LinTag tagOrNull = this.input.value().get(key); + return Optional + .ofNullable(tagOrNull) + .map(tag -> codec.parse(this.context.ops(), tag)) + .flatMap(result -> result.resultOrPartial(error -> this.problemReporter.report(() -> "Failed to decode value '" + tagOrNull + "' from field '" + key + "': " + error))); + } + + @Override + public @NonNull Optional read(final @NonNull MapCodec mapCodec) { + return this.context.ops() + .getMap(this.input) + .flatMap(mapLike -> mapCodec.decode(this.context.ops(), mapLike)) + .resultOrPartial(error -> this.problemReporter.report(() -> "Failed to decode from map: " + error)); + } + + @Override + public @NonNull Optional child(final @NonNull String key) { + return Optional.ofNullable(this.input.findTag(key, LinTagType.compoundTag())).map(tag -> tag.value().isEmpty() + ? this.context.empty() + : new LinValueInput(this.context, this.problemReporter.forChild(new ProblemReporter.FieldPathElement(key)), tag)); + } + + @Override + public @NonNull ValueInput childOrEmpty(final @NonNull String key) { + return this.child(key).orElse(this.context.empty()); + } + + @Override + public @NonNull Optional childrenList(final @NonNull String key) { + //noinspection unchecked + return Optional + .ofNullable(this.input.value().get(key)) + .filter(linTag -> linTag instanceof LinListTag) + .map(tag -> ((LinListTag>) tag)) + .map(listTag -> listTag.value().isEmpty() + ? this.context.emptyChildList() + : new ListWrapper(this.problemReporter, key, listTag, this.context)); + } + + @Override + public @NonNull ValueInputList childrenListOrEmpty(final @NonNull String key) { + return this.childrenList(key).orElse(this.context.emptyChildList()); + } + + @Override + public @NonNull Optional> list(final @NonNull String key, final @NonNull Codec codec) { + //noinspection unchecked + return Optional + .ofNullable(this.input.value().get(key)) + .filter(linTag -> linTag instanceof LinListTag) + .map(tag -> ((LinListTag>) tag)) + .map(listTag -> listTag.value().isEmpty() + ? this.context.emptySafelyTypedList() + : new TypedListWrapper<>(this.problemReporter, key, listTag, this.context, codec)); + } + + @Override + public @NonNull TypedInputList listOrEmpty(final @NonNull String key, final @NonNull Codec codec) { + return this.list(key, codec).orElse(this.context.emptySafelyTypedList()); + } + + @Override + public boolean getBooleanOr(final @NonNull String key, final boolean defaultValue) { + if (this.input.value().get(key) instanceof LinNumberTag numberTag) { + return numberTag.value().byteValue() != 0; + } + return defaultValue; + } + + @Override + public byte getByteOr(final @NonNull String key, final byte defaultValue) { + if (this.input.value().get(key) instanceof LinByteTag byteTag) { + return byteTag.valueAsByte(); + } + return defaultValue; + } + + @Override + public int getShortOr(final @NonNull String key, final short defaultValue) { + if (this.input.value().get(key) instanceof LinShortTag shortTag) { + return shortTag.valueAsShort(); + } + return defaultValue; + } + + @Override + public @NonNull Optional getInt(final @NonNull String key) { + if (this.input.value().get(key) instanceof LinNumberTag numberTag) { + return Optional.of(numberTag.value().intValue()); + } + return Optional.empty(); + } + + @Override + public int getIntOr(final @NonNull String key, final int defaultValue) { + if (this.input.value().get(key) instanceof LinNumberTag numberTag) { + return numberTag.value().intValue(); + } + return defaultValue; + } + + @Override + public long getLongOr(final @NonNull String key, final long defaultValue) { + if (this.input.value().get(key) instanceof LinNumberTag numberTag) { + return numberTag.value().longValue(); + } + return defaultValue; + } + + @Override + public @NonNull Optional getLong(final @NonNull String key) { + if (this.input.value().get(key) instanceof LinNumberTag numberTag) { + return Optional.of(numberTag.value().longValue()); + } + return Optional.empty(); + } + + @Override + public float getFloatOr(final @NonNull String key, final float defaultValue) { + if (this.input.value().get(key) instanceof LinNumberTag numberTag) { + return numberTag.value().floatValue(); + } + return defaultValue; + } + + @Override + public double getDoubleOr(final @NonNull String key, final double defaultValue) { + if (this.input.value().get(key) instanceof LinNumberTag numberTag) { + return numberTag.value().doubleValue(); + } + return defaultValue; + } + + @Override + public @NonNull Optional getString(final @NonNull String key) { + if (this.input.value().get(key) instanceof LinStringTag stringTag) { + return Optional.of(stringTag.value()); + } + return Optional.empty(); + } + + @Override + public @NonNull String getStringOr(final @NonNull String key, final @NonNull String defaultValue) { + if (this.input.value().get(key) instanceof LinStringTag stringTag) { + return stringTag.value(); + } + return defaultValue; + } + + @Override + public @NonNull Optional getIntArray(final @NonNull String key) { + if (this.input.value().get(key) instanceof LinIntArrayTag intArrayTag) { + return Optional.of(intArrayTag.value()); + } + return Optional.empty(); + } + + @Override + public HolderLookup.@NonNull Provider lookup() { + return this.context.lookup(); + } + + private record ValueInputContext(HolderLookup.Provider lookup, DynamicOps> ops, ValueInput empty, + ValueInputList emptyChildList, TypedInputList emptyTypedList) { + + ValueInputContext(HolderLookup.Provider lookup) { + this( + lookup, + lookup.createSerializationContext(LinOps.INSTANCE), + new EmptyValueInput(lookup, new EmptyValueInputList(), new EmptyTypedInputList()), + new EmptyValueInputList(), + new EmptyTypedInputList() + ); + } + + public TypedInputList emptySafelyTypedList() { + //noinspection unchecked + return (TypedInputList) this.emptyTypedList; + } + + } + + private record ListWrapper(ProblemReporter problemReporter, String name, LinListTag> list, + ValueInputContext context) implements ValueInputList { + + @Override + public boolean isEmpty() { + return this.list.value().isEmpty(); + } + + @Override + public @NonNull Stream stream() { + return Streams.mapWithIndex( + this.list.value().stream(), (tag, index) -> { + if (tag instanceof LinCompoundTag compoundTag) { + return compoundTag.value().isEmpty() ? this.context.empty() : new LinValueInput( + this.context, + this.problemReporter.forChild(new ProblemReporter.IndexedFieldPathElement( + this.name, + (int) index + )), + compoundTag + ); + } + this.problemReporter.report(() -> "Expected list '" + this.name + "' to contain at index " + index + " value of type " + LinTagType.compoundTag() + ", but got " + tag.type()); + return null; + } + ).filter(Objects::nonNull); + } + + @Override + public @NonNull Iterator iterator() { + final Iterator> iterator = this.list.value().iterator(); + return new AbstractIterator<>() { + private int index; + + @Override + protected @Nullable ValueInput computeNext() { + while (iterator.hasNext()) { + LinTag tag = iterator.next(); + int i = this.index++; + if (tag instanceof LinCompoundTag compoundTag) { + return compoundTag.value().isEmpty() ? context.empty() : new LinValueInput( + context, + problemReporter.forChild(new ProblemReporter.IndexedFieldPathElement(name, i)), + compoundTag + ); + } + problemReporter.report(() -> "Expected list '" + name + "' to contain at index " + index + " value of type " + LinTagType.compoundTag() + ", but got " + tag.type()); + } + return this.endOfData(); + } + }; + } + + } + + private record TypedListWrapper(ProblemReporter problemReporter, String name, LinListTag> list, + ValueInputContext context, Codec codec) implements TypedInputList { + + @Override + public boolean isEmpty() { + return this.list.value().isEmpty(); + } + + @Override + public @NonNull Stream stream() { + return Streams.mapWithIndex( + this.list.value().stream(), (tag, index) -> this.codec + .parse(this.context.ops(), tag) + .resultOrPartial(error -> this.problemReporter.report(() -> "Failed to decode value '" + tag + "' from field '" + name + "' at index " + index + ": " + error)) + .orElse(null) + ).filter(Objects::nonNull); + } + + @Override + public @NonNull Iterator iterator() { + final ListIterator> iterator = this.list.value().listIterator(); + return new AbstractIterator<>() { + @Override + protected @Nullable T computeNext() { + while (true) { + if (iterator.hasNext()) { + int index = iterator.nextIndex(); + LinTag tag = iterator.next(); + switch (codec.parse(context.ops(), tag)) { + case DataResult.Success success: + return success.value(); + case DataResult.Error error: + problemReporter.report(() -> "Failed to decode value '" + tag + "' from field '" + name + "' at index " + index + ":" + " " + error); + if (error.partialValue().isEmpty()) { + continue; + } + return error.partialValue().get(); + } + } + return this.endOfData(); + } + } + }; + } + + } + + private static final class EmptyValueInputList implements ValueInputList { + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public @NonNull Stream stream() { + return Stream.empty(); + } + + @Override + public @NonNull Iterator iterator() { + return Collections.emptyIterator(); + } + + } + + private static final class EmptyTypedInputList implements TypedInputList { + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public @NonNull Stream stream() { + return Stream.empty(); + } + + @Override + public @NonNull Iterator iterator() { + return Collections.emptyIterator(); + } + + } + + private record EmptyValueInput(HolderLookup.Provider lookup, ValueInputList emptyChildList, + TypedInputList emptyTypedList) implements ValueInput { + + @Override + public @NonNull Optional read(final @NonNull String key, final @NonNull Codec codec) { + return Optional.empty(); + } + + @Override + public @NonNull Optional read(final @NonNull MapCodec mapCodec) { + return Optional.empty(); + } + + @Override + public @NonNull Optional child(final @NonNull String s) { + return Optional.empty(); + } + + @Override + public @NonNull ValueInput childOrEmpty(final @NonNull String s) { + return EmptyValueInput.this; + } + + @Override + public @NonNull Optional childrenList(final @NonNull String s) { + return Optional.empty(); + } + + @Override + public @NonNull ValueInputList childrenListOrEmpty(final @NonNull String s) { + return this.emptyChildList; + } + + @Override + public @NonNull Optional> list(final @NonNull String s, final @NonNull Codec codec) { + return Optional.empty(); + } + + @Override + public @NonNull TypedInputList listOrEmpty(final @NonNull String s, final @NonNull Codec codec) { + //noinspection unchecked + return (TypedInputList) this.emptyTypedList; + } + + @Override + public boolean getBooleanOr(final @NonNull String s, final boolean defaultValue) { + return defaultValue; + } + + @Override + public byte getByteOr(final @NonNull String s, final byte defaultValue) { + return defaultValue; + } + + @Override + public int getShortOr(final @NonNull String s, final short defaultValue) { + return defaultValue; + } + + @Override + public @NonNull Optional getInt(final @NonNull String s) { + return Optional.empty(); + } + + @Override + public int getIntOr(final @NonNull String s, final int defaultValue) { + return defaultValue; + } + + @Override + public long getLongOr(final @NonNull String s, final long defaultValue) { + return defaultValue; + } + + @Override + public @NonNull Optional getLong(final @NonNull String s) { + return Optional.empty(); + } + + @Override + public float getFloatOr(final @NonNull String s, final float defaultValue) { + return defaultValue; + } + + @Override + public double getDoubleOr(final @NonNull String s, final double defaultValue) { + return defaultValue; + } + + @Override + public @NonNull Optional getString(final @NonNull String s) { + return Optional.empty(); + } + + @Override + public @NonNull String getStringOr(final @NonNull String s, final @NonNull String defaultValue) { + return defaultValue; + } + + @Override + public @NonNull Optional getIntArray(final @NonNull String s) { + return Optional.empty(); + } + + } + +} diff --git a/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/LinValueOutput.java b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/LinValueOutput.java new file mode 100644 index 0000000000..8196cb2514 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/LinValueOutput.java @@ -0,0 +1,302 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.MapCodec; +import net.minecraft.core.HolderLookup; +import net.minecraft.util.ProblemReporter; +import net.minecraft.world.level.storage.ValueOutput; +import org.enginehub.linbus.tree.LinByteTag; +import org.enginehub.linbus.tree.LinCompoundTag; +import org.enginehub.linbus.tree.LinDoubleTag; +import org.enginehub.linbus.tree.LinFloatTag; +import org.enginehub.linbus.tree.LinIntArrayTag; +import org.enginehub.linbus.tree.LinIntTag; +import org.enginehub.linbus.tree.LinLongTag; +import org.enginehub.linbus.tree.LinShortTag; +import org.enginehub.linbus.tree.LinStringTag; +import org.enginehub.linbus.tree.LinTag; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.VisibleForTesting; + +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * ValueOutput implementation for direct LinBus interaction. + * Basically a copy of {@link net.minecraft.world.level.storage.TagValueOutput}, but for Lin (with changes so it actually works). + *

+ * Given LinBus is extremely immutable (except it's builders of course), this ValueOutput needs intermediate types and collection + * maps to work. The ValueOutput expects mutability of it's underlying data structures (for example when creating lists or + * child compounds as those are modified in subsequent method calls on the ValueOutput but must be reflected in their + * parents). + */ +public class LinValueOutput implements ValueOutput { + + private final ProblemReporter problemReporter; + private final DynamicOps> ops; + private final Map collector; + + LinValueOutput(final ProblemReporter reporter, final DynamicOps> ops) { + this.problemReporter = reporter; + this.ops = ops; + this.collector = new LinkedHashMap<>(); + } + + public static LinValueOutput createWithContext(ProblemReporter reporter, HolderLookup.Provider lookup) { + return new LinValueOutput(reporter, lookup.createSerializationContext(LinOps.INSTANCE)); + } + + @Override + public void store(final @NotNull String key, final @NotNull Codec codec, final @NotNull T value) { + DataResult> result = codec.encodeStart(this.ops, value); + switch (result) { + case DataResult.Success> success -> this.collector.put(key, PendingEntry.ofTag(success.value())); + case DataResult.Error> error -> { + this.problemReporter.report(() -> "Failed to encode value '" + value + "' to field '" + key + "': " + error.message()); + error.partialValue().ifPresent(tag -> this.collector.put(key, PendingEntry.ofTag(tag))); + } + } + } + + @Override + public void storeNullable(final @NotNull String key, final @NotNull Codec codec, @Nullable final T value) { + if (value != null) { + this.store(key, codec, value); + } + } + + @Override + public void store(final @NotNull MapCodec mapCodec, final @NotNull T value) { + DataResult> result = mapCodec.encoder().encodeStart(this.ops, value); + switch (result) { + case DataResult.Success> success -> { + if (success.value() instanceof LinCompoundTag compoundTag) { + this.merge(compoundTag); + } + } + case DataResult.Error> error -> { + this.problemReporter.report(() -> "Failed to merge value '" + value + "' to an object: " + error.message()); + error.partialValue().filter(tag -> tag instanceof LinCompoundTag) + .ifPresent(tag -> merge((LinCompoundTag) tag)); + } + } + } + + @VisibleForTesting + void merge(LinCompoundTag other) { + other.value().forEach((key, tag) -> { + if (tag instanceof LinCompoundTag compoundTag && this.collector.containsKey(key)) { + PendingEntry entry = this.collector.get(key); + if (entry instanceof PendingTagEntry(LinTag value) && value instanceof LinCompoundTag storedCompound) { + this.collector.put(key, PendingEntry.ofTag(merge(compoundTag, storedCompound))); + return; + } + if (entry instanceof PendingLinValueOutputEntry(LinValueOutput valueOutput)) { + this.collector.put(key, PendingEntry.ofTag(merge(compoundTag, valueOutput.buildResult()))); + return; + } + } + this.collector.put(key, PendingEntry.ofTag(tag)); + }); + } + + private static LinCompoundTag merge(LinCompoundTag source, LinCompoundTag target) { + LinCompoundTag.Builder builder = target.toBuilder(); + source.value().forEach((key, tag) -> { + if (target.value().get(key) instanceof LinCompoundTag targetCompoundTag && tag instanceof LinCompoundTag sourceCompoundTag) { + builder.put(key, merge(sourceCompoundTag, targetCompoundTag)); + return; + } + builder.put(key, tag); + }); + return builder.build(); + } + + @Override + public void putBoolean(final @NotNull String key, final boolean value) { + this.collector.put(key, PendingEntry.ofTag(LinByteTag.of(value ? (byte) 1 : 0))); + } + + @Override + public void putByte(final @NotNull String key, final byte value) { + this.collector.put(key, PendingEntry.ofTag(LinByteTag.of(value))); + } + + @Override + public void putShort(final @NotNull String key, final short value) { + this.collector.put(key, PendingEntry.ofTag(LinShortTag.of(value))); + } + + @Override + public void putInt(final @NotNull String key, final int value) { + this.collector.put(key, PendingEntry.ofTag(LinIntTag.of(value))); + } + + @Override + public void putLong(final @NotNull String key, final long value) { + this.collector.put(key, PendingEntry.ofTag(LinLongTag.of(value))); + } + + @Override + public void putFloat(final @NotNull String key, final float value) { + this.collector.put(key, PendingEntry.ofTag(LinFloatTag.of(value))); + } + + @Override + public void putDouble(final @NotNull String key, final double value) { + this.collector.put(key, PendingEntry.ofTag(LinDoubleTag.of(value))); + } + + @Override + public void putString(final @NotNull String key, final @NotNull String value) { + this.collector.put(key, PendingEntry.ofTag(LinStringTag.of(value))); + } + + @Override + public void putIntArray(final @NotNull String key, final int @NotNull [] value) { + this.collector.put(key, PendingEntry.ofTag(LinIntArrayTag.of(value))); + } + + @Override + public @NotNull ValueOutput child(final @NotNull String key) { + LinValueOutput output = new LinValueOutput( + this.problemReporter.forChild(new ProblemReporter.FieldPathElement(key)), + this.ops + ); + this.collector.put(key, PendingEntry.ofLinValueOutputEntry(output)); + return output; + } + + @Override + public @NotNull ValueOutputList childrenList(final @NotNull String key) { + List list = new LinkedList<>(); + this.collector.put(key, PendingEntry.ofList(list)); + return new ListWrapper(key, this.problemReporter, this.ops, list); + } + + @Override + public @NotNull TypedOutputList list(final @NotNull String key, final @NotNull Codec codec) { + final List list = new LinkedList<>(); + this.collector.put(key, PendingEntry.ofList(list)); + return new TypedListWrapper<>(this.problemReporter, key, this.ops, codec, list); + } + + @Override + public void discard(final @NotNull String key) { + this.collector.remove(key); + } + + @Override + public boolean isEmpty() { + return this.collector.isEmpty(); + } + + public LinCompoundTag buildResult() { + if (this.isEmpty()) { + return LinCompoundTag.builder().build(); + } + return LinCompoundTag.of(this.collector.entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, entry -> unwrapPendingEntry(entry.getValue()))) + ); + } + + public LinCompoundTag.Builder toBuilder() { + LinCompoundTag.Builder builder = LinCompoundTag.builder(); + this.collector.forEach((key, value) -> builder.put(key, unwrapPendingEntry(value))); + return builder; + } + + private static LinTag unwrapPendingEntry(final PendingEntry entry) { + return switch (entry) { + case PendingTagEntry e -> e.tag(); + case PendingLinValueOutputEntry e -> e.valueOutput().buildResult(); + case PendingListEntry e -> LinOps.rawTagListToLinList( + e.entries().stream().map(LinValueOutput::unwrapPendingEntry).collect(Collectors.toList()) + ); + }; + } + + private record ListWrapper(String fieldName, ProblemReporter problemReporter, DynamicOps> ops, + List list) implements ValueOutputList { + + @Override + public @NotNull ValueOutput addChild() { + LinValueOutput valueOutput = new LinValueOutput( + this.problemReporter.forChild( + new ProblemReporter.IndexedFieldPathElement(this.fieldName, this.list.size() - 1) + ), + this.ops + ); + this.list.add(PendingEntry.ofLinValueOutputEntry(valueOutput)); + return valueOutput; + } + + @Override + public void discardLast() { + this.list.removeLast(); + } + + @Override + public boolean isEmpty() { + return this.list.isEmpty(); + } + + } + + private record TypedListWrapper(ProblemReporter problemReporter, String name, DynamicOps> ops, Codec codec, + List list) implements TypedOutputList { + + @Override + public void add(final T value) { + DataResult> dataResult = this.codec.encodeStart(this.ops, value); + switch (dataResult) { + case DataResult.Success> success -> this.list.add(PendingEntry.ofTag(success.value())); + case DataResult.Error> error -> { + this.problemReporter.report(() -> "Failed to append value '" + value + "' to list '" + this.name + "':" + error.message()); + error.partialValue().map(PendingEntry::ofTag).ifPresent(list::add); + } + } + } + + @Override + public boolean isEmpty() { + return list.isEmpty(); + } + + } + + private sealed interface PendingEntry permits PendingTagEntry, PendingListEntry, PendingLinValueOutputEntry { + + static PendingEntry ofTag(LinTag tag) { + return new PendingTagEntry(tag); + } + + static PendingEntry ofList(List list) { + return new PendingListEntry(list); + } + + static PendingEntry ofLinValueOutputEntry(LinValueOutput valueOutput) { + return new PendingLinValueOutputEntry(valueOutput); + } + + } + + private record PendingTagEntry(LinTag tag) implements PendingEntry { + + } + + private record PendingListEntry(List entries) implements PendingEntry { + + } + + private record PendingLinValueOutputEntry(LinValueOutput valueOutput) implements PendingEntry { + + } + +} diff --git a/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightChunkAccessProxy.java b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightChunkAccessProxy.java new file mode 100644 index 0000000000..26ae27928e --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightChunkAccessProxy.java @@ -0,0 +1,624 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1; + +import ca.spottedleaf.moonrise.patches.starlight.light.SWMRNibbleArray; +import com.fastasyncworldedit.core.util.ReflectionUtils; +import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.shorts.ShortList; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.HolderLookup; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.util.ProblemReporter; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.ClipBlockStateContext; +import net.minecraft.world.level.ClipContext; +import net.minecraft.world.level.LevelHeightAccessor; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.biome.BiomeGenerationSettings; +import net.minecraft.world.level.biome.BiomeResolver; +import net.minecraft.world.level.biome.Climate; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.UpgradeData; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.gameevent.GameEventListenerRegistry; +import net.minecraft.world.level.levelgen.BelowZeroRetrogen; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.level.levelgen.NoiseChunk; +import net.minecraft.world.level.levelgen.blending.BlendingData; +import net.minecraft.world.level.levelgen.structure.Structure; +import net.minecraft.world.level.levelgen.structure.StructureStart; +import net.minecraft.world.level.lighting.ChunkSkyLightSources; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.phys.shapes.VoxelShape; +import net.minecraft.world.ticks.TickContainerAccess; +import sun.misc.Unsafe; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.Stream; + +/** + * This exists solely for the {@link PaperweightChunkAccessProxy#markPosForPostprocessing(BlockPos)} override, as the way we + * handle feature/structure generation means the chunks returned in {@link FaweBlockStateListPopulator#getChunk(BlockPos)} (and + * other getChunk) are not {@link net.minecraft.world.level.chunk.ProtoChunk} types, so do not override the + * {@link ChunkAccess#markPosForPostprocessing(BlockPos)} function. + */ +@SuppressWarnings({"removal", "deprecation"}) +public final class PaperweightChunkAccessProxy extends ChunkAccess { + + ChunkAccess parent; + + @SuppressWarnings("DataFlowIssue") + private PaperweightChunkAccessProxy() { + super(null, null, null, null, -1, null, null); + throw new IllegalStateException("Cannot be instantiated"); + } + + public static PaperweightChunkAccessProxy getInstance() { + Unsafe unsafe = ReflectionUtils.getUnsafe(); + + try { + return (PaperweightChunkAccessProxy) unsafe.allocateInstance(PaperweightChunkAccessProxy.class); + } catch (InstantiationException e) { + throw new RuntimeException(e); + } + } + + @Override + public int hashCode() { + return parent.hashCode(); + } + + @Override + public boolean equals(final Object obj) { + return parent.equals((obj instanceof PaperweightChunkAccessProxy ? ((PaperweightChunkAccessProxy) obj).parent : obj)); + } + + @Override + public String toString() { + return parent.toString(); + } + + @Override + public @Nonnull BlockState getBlockState(final @Nonnull BlockPos pos) { + return parent.getBlockState(pos); + } + + @Override + public @Nullable BlockState getBlockStateIfLoaded(final @Nonnull BlockPos blockPos) { + return parent.getBlockStateIfLoaded(blockPos); + } + + @Override + public @Nullable Block getBlockIfLoaded(final @Nonnull BlockPos blockposition) { + return parent.getBlockIfLoaded(blockposition); + } + + @Override + public @Nullable FluidState getFluidIfLoaded(final @Nonnull BlockPos blockPos) { + return parent.getFluidIfLoaded(blockPos); + } + + @Override + public @Nonnull FluidState getFluidState(final @Nonnull BlockPos pos) { + return parent.getFluidState(pos); + } + + @Override + public int getLightEmission(final @Nonnull BlockPos pos) { + return parent.getLightEmission(pos); + } + + @Override + public @Nonnull Stream getBlockStates(final @Nonnull AABB box) { + return parent.getBlockStates(box); + } + + @Override + public @Nonnull BlockHitResult isBlockInLine(final @Nonnull ClipBlockStateContext context) { + return parent.isBlockInLine(context); + } + + @Override + public @Nonnull BlockHitResult clip(final @Nonnull ClipContext raytrace1, final @Nonnull BlockPos blockposition) { + return parent.clip(raytrace1, blockposition); + } + + @Override + public @Nonnull BlockHitResult clip( + final @Nonnull ClipContext raytrace1, + final @Nonnull BlockPos blockposition, + final @Nullable Predicate canCollide + ) { + return parent.clip(raytrace1, blockposition, canCollide); + } + + @Override + public @Nonnull BlockHitResult clip(final @Nonnull ClipContext context) { + return parent.clip(context); + } + + @Override + public @Nonnull BlockHitResult clip( + final @Nonnull ClipContext context, + final @Nullable Predicate canCollide + ) { + return parent.clip(context, canCollide); + } + + @Override + public @Nullable BlockHitResult clipWithInteractionOverride( + final @Nonnull Vec3 start, + final @Nonnull Vec3 end, + final @Nonnull BlockPos pos, + final @Nonnull VoxelShape shape, + final @Nonnull BlockState state + ) { + return parent.clipWithInteractionOverride(start, end, pos, shape, state); + } + + @Override + public double getBlockFloorHeight( + final @Nonnull VoxelShape blockCollisionShape, + final @Nonnull Supplier belowBlockCollisionShapeGetter + ) { + return parent.getBlockFloorHeight(blockCollisionShape, belowBlockCollisionShapeGetter); + } + + @Override + public double getBlockFloorHeight(final @Nonnull BlockPos pos) { + return parent.getBlockFloorHeight(pos); + } + + @Override + public BlockEntity getBlockEntity(final @Nonnull BlockPos pos) { + return parent.getBlockEntity(pos); + } + + @Override + public @Nonnull Optional getBlockEntity( + final @Nonnull BlockPos pos, + final @Nonnull BlockEntityType type + ) { + return parent.getBlockEntity(pos, type); + } + + @Override + public @Nonnull SWMRNibbleArray[] starlight$getBlockNibbles() { + return parent.starlight$getBlockNibbles(); + } + + @Override + public void starlight$setBlockNibbles(final @Nonnull SWMRNibbleArray[] nibbles) { + parent.starlight$setBlockNibbles(nibbles); + } + + @Override + public @Nonnull SWMRNibbleArray[] starlight$getSkyNibbles() { + return parent.starlight$getBlockNibbles(); + } + + @Override + public void starlight$setSkyNibbles(final @Nonnull SWMRNibbleArray[] nibbles) { + parent.starlight$setSkyNibbles(nibbles); + } + + @Override + public @Nonnull boolean[] starlight$getSkyEmptinessMap() { + return parent.starlight$getSkyEmptinessMap(); + } + + @Override + public void starlight$setSkyEmptinessMap(final @Nonnull boolean[] emptinessMap) { + parent.starlight$setSkyEmptinessMap(emptinessMap); + } + + @Override + public @Nonnull boolean[] starlight$getBlockEmptinessMap() { + return parent.starlight$getBlockEmptinessMap(); + } + + @Override + public void starlight$setBlockEmptinessMap(final @Nonnull boolean[] emptinessMap) { + parent.starlight$setBlockEmptinessMap(emptinessMap); + } + + @Override + public @Nonnull GameEventListenerRegistry getListenerRegistry(final int sectionY) { + return parent.getListenerRegistry(sectionY); + } + + @Override + public @Nonnull BlockState getBlockState(final int i, final int i1, final int i2) { + return parent.getBlockState(i, i1, i2); + } + + @Override + public @Nullable BlockState setBlockState(final @Nonnull BlockPos pos, final @Nonnull BlockState state) { + return parent.setBlockState(pos, state); + } + + @Override + public @Nullable BlockState setBlockState( + final @Nonnull BlockPos blockPos, + final @Nonnull BlockState blockState, + final int i + ) { + return parent.setBlockState(blockPos, blockState, i); + } + + @Override + public void setBlockEntity(final @Nonnull BlockEntity blockEntity) { + parent.setBlockEntity(blockEntity); + } + + @Override + public void addEntity(final @Nonnull Entity entity) { + parent.addEntity(entity); + } + + @Override + public int getHighestFilledSectionIndex() { + return parent.getHighestFilledSectionIndex(); + } + + @Override + public int getHighestSectionPosition() { + return parent.getHighestSectionPosition(); + } + + @Override + public @Nonnull Set getBlockEntitiesPos() { + return parent.getBlockEntitiesPos(); + } + + @Override + public @Nonnull LevelChunkSection[] getSections() { + return parent.getSections(); + } + + @Override + public @Nonnull LevelChunkSection getSection(final int index) { + return parent.getSection(index); + } + + @Override + public @Nonnull Collection> getHeightmaps() { + return parent.getHeightmaps(); + } + + @Override + public void setHeightmap(final @Nonnull Heightmap.Types type, @Nonnull final long[] data) { + parent.setHeightmap(type, data); + } + + @Override + public @Nonnull Heightmap getOrCreateHeightmapUnprimed(final @Nonnull Heightmap.Types type) { + return parent.getOrCreateHeightmapUnprimed(type); + } + + @Override + public boolean hasPrimedHeightmap(final @Nonnull Heightmap.Types type) { + return parent.hasPrimedHeightmap(type); + } + + @Override + public int getHeight(final @Nonnull Heightmap.Types type, final int x, final int z) { + return parent.getHeight(type, x, z); + } + + @Override + public @Nonnull ChunkPos getPos() { + return parent.getPos(); + } + + @Override + public @Nullable StructureStart getStartForStructure(final @Nonnull Structure structure) { + return parent.getStartForStructure(structure); + } + + @Override + public void setStartForStructure(final @Nonnull Structure structure, final @Nonnull StructureStart structureStart) { + parent.setStartForStructure(structure, structureStart); + } + + @Override + public @Nonnull Map getAllStarts() { + return parent.getAllStarts(); + } + + @Override + public void setAllStarts(final @Nonnull Map structureStarts) { + parent.setAllStarts(structureStarts); + } + + @Override + public @Nonnull LongSet getReferencesForStructure(final @Nonnull Structure structure) { + return parent.getReferencesForStructure(structure); + } + + @Override + public void addReferenceForStructure(final @Nonnull Structure structure, final long reference) { + parent.addReferenceForStructure(structure, reference); + } + + @Override + public @Nonnull Map getAllReferences() { + return parent.getAllReferences(); + } + + @Override + public void setAllReferences(final @Nonnull Map structureReferencesMap) { + parent.setAllReferences(structureReferencesMap); + } + + @Override + public boolean isYSpaceEmpty(final int startY, final int endY) { + return parent.isYSpaceEmpty(startY, endY); + } + + @Override + public void markUnsaved() { + parent.markUnsaved(); + } + + @Override + public boolean tryMarkSaved() { + return parent.tryMarkSaved(); + } + + @Override + public boolean isUnsaved() { + return parent.isUnsaved(); + } + + @Override + public @Nonnull ChunkStatus getPersistedStatus() { + return parent.getPersistedStatus(); + } + + @Override + public @Nonnull ChunkStatus getHighestGeneratedStatus() { + return parent.getHighestGeneratedStatus(); + } + + @Override + public void removeBlockEntity(final @Nonnull BlockPos blockPos) { + parent.removeBlockEntity(blockPos); + } + + @Override + public void markPosForPostprocessing(final @Nonnull BlockPos pos) { + //Do nothing. ALL THIS FOR THIS METHOD :) + } + + @Override + public @Nonnull ShortList[] getPostProcessing() { + return parent.getPostProcessing(); + } + + @Override + public void addPackedPostProcess(final @Nonnull ShortList offsets, final int index) { + parent.addPackedPostProcess(offsets, index); + } + + @Override + public void setBlockEntityNbt(final @Nonnull CompoundTag tag) { + parent.setBlockEntityNbt(tag); + } + + @Override + public @Nullable CompoundTag getBlockEntityNbt(final @Nonnull BlockPos pos) { + return parent.getBlockEntityNbt(pos); + } + + @Override + public @Nullable CompoundTag getBlockEntityNbtForSaving( + final @Nonnull BlockPos blockPos, + final @Nonnull HolderLookup.Provider provider + ) { + return parent.getBlockEntityNbtForSaving(blockPos, provider); + } + + @Override + public void findBlocks( + final @Nonnull Predicate predicate, + final @Nonnull BiConsumer output + ) { + parent.findBlocks(predicate, output); + } + + @Override + public @Nonnull TickContainerAccess getBlockTicks() { + return parent.getBlockTicks(); + } + + @Override + public @Nonnull TickContainerAccess getFluidTicks() { + return parent.getFluidTicks(); + } + + @Override + public boolean canBeSerialized() { + return parent.canBeSerialized(); + } + + @Override + public @Nonnull PackedTicks getTicksForSerialization(final long l) { + return parent.getTicksForSerialization(l); + } + + @Override + public @Nonnull UpgradeData getUpgradeData() { + return parent.getUpgradeData(); + } + + @Override + public boolean isOldNoiseGeneration() { + return parent.isOldNoiseGeneration(); + } + + @Override + public @Nullable BlendingData getBlendingData() { + return parent.getBlendingData(); + } + + @Override + public long getInhabitedTime() { + return parent.getInhabitedTime(); + } + + @Override + public void incrementInhabitedTime(final long amount) { + parent.incrementInhabitedTime(amount); + } + + @Override + public void setInhabitedTime(final long inhabitedTime) { + parent.setInhabitedTime(inhabitedTime); + } + + @Override + public boolean isLightCorrect() { + return parent.isLightCorrect(); + } + + @Override + public void setLightCorrect(final boolean lightCorrect) { + parent.setLightCorrect(lightCorrect); + } + + @Override + public int getMinY() { + return parent.getMinY(); + } + + @Override + public int getHeight() { + return parent.getHeight(); + } + + @Override + public @Nonnull NoiseChunk getOrCreateNoiseChunk(final @Nonnull Function noiseChunkCreator) { + return parent.getOrCreateNoiseChunk(noiseChunkCreator); + } + + @Override + public @Nonnull BiomeGenerationSettings carverBiome(final @Nonnull Supplier caverBiomeSettingsSupplier) { + return parent.carverBiome(caverBiomeSettingsSupplier); + } + + @Override + public @Nonnull Holder getNoiseBiome(final int x, final int y, final int z) { + return parent.getNoiseBiome(x, y, z); + } + + @Override + public void fillBiomesFromNoise(final @Nonnull BiomeResolver resolver, final @Nonnull Climate.Sampler sampler) { + parent.fillBiomesFromNoise(resolver, sampler); + } + + @Override + public boolean hasAnyStructureReferences() { + return parent.hasAnyStructureReferences(); + } + + @Override + public @Nullable BelowZeroRetrogen getBelowZeroRetrogen() { + return parent.getBelowZeroRetrogen(); + } + + @Override + public boolean isUpgrading() { + return parent.isUpgrading(); + } + + @Override + public @Nonnull LevelHeightAccessor getHeightAccessorForGeneration() { + return parent.getHeightAccessorForGeneration(); + } + + @Override + public void initializeLightSources() { + parent.initializeLightSources(); + } + + @Override + public @Nonnull ChunkSkyLightSources getSkyLightSources() { + return parent.getSkyLightSources(); + } + + @Override + public @Nonnull ProblemReporter.PathElement problemPath() { + return parent.problemPath(); + } + + @Override + public int getMaxY() { + return parent.getMaxY(); + } + + @Override + public int getSectionsCount() { + return parent.getSectionsCount(); + } + + @Override + public int getMinSectionY() { + return parent.getMinSectionY(); + } + + @Override + public int getMaxSectionY() { + return parent.getMaxSectionY(); + } + + @Override + public boolean isInsideBuildHeight(final int y) { + return parent.isInsideBuildHeight(y); + } + + @Override + public boolean isOutsideBuildHeight(final @Nonnull BlockPos pos) { + return parent.isOutsideBuildHeight(pos); + } + + @Override + public boolean isOutsideBuildHeight(final int y) { + return parent.isOutsideBuildHeight(y); + } + + @Override + public int getSectionIndex(final int y) { + return parent.getSectionIndex(y); + } + + @Override + public int getSectionIndexFromSectionY(final int coord) { + return parent.getSectionIndexFromSectionY(coord); + } + + @Override + public int getSectionYFromSectionIndex(final int index) { + return parent.getSectionYFromSectionIndex(index); + } + +} diff --git a/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightFaweAdapter.java b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightFaweAdapter.java new file mode 100644 index 0000000000..b0807a0298 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightFaweAdapter.java @@ -0,0 +1,935 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1; + +import com.fastasyncworldedit.bukkit.adapter.FaweAdapter; +import com.fastasyncworldedit.bukkit.adapter.NMSRelighterFactory; +import com.fastasyncworldedit.core.FaweCache; +import com.fastasyncworldedit.core.entity.LazyBaseEntity; +import com.fastasyncworldedit.core.extent.processor.PlacementStateProcessor; +import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory; +import com.fastasyncworldedit.core.nbt.FaweCompoundTag; +import com.fastasyncworldedit.core.queue.IBatchProcessor; +import com.fastasyncworldedit.core.queue.IChunkGet; +import com.fastasyncworldedit.core.queue.implementation.packet.ChunkPacket; +import com.fastasyncworldedit.core.util.FoliaUtil; +import com.fastasyncworldedit.core.util.TaskManager; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Sets; +import com.mojang.serialization.Codec; +import com.sk89q.jnbt.Tag; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; +import com.sk89q.worldedit.blocks.BaseItemStack; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; +import com.sk89q.worldedit.bukkit.adapter.impl.v26_1.PaperweightAdapter; +import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1.*; +import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1.FaweBlockStateListPopulator; +import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1.PaperweightFaweWorldNativeAccess; +import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1.PaperweightMapChunkUtil; +import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1.PaperweightPlacementStateProcessor; +import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1.PaperweightPlatformAdapter; +import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1.PaperweightPostProcessor; +import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1.PaperweightStarlightRelighterFactory; +import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1.regen.PaperweightRegen; +import com.sk89q.worldedit.bukkit.adapter.impl.v26_1.PaperweightBlockMaterial; +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.function.mask.BlockTypeMask; +import com.sk89q.worldedit.internal.block.BlockStateIdAccess; +import com.sk89q.worldedit.internal.util.LogManagerCompat; +import com.sk89q.worldedit.internal.wna.WorldNativeAccess; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.registry.state.BooleanProperty; +import com.sk89q.worldedit.registry.state.DirectionalProperty; +import com.sk89q.worldedit.registry.state.EnumProperty; +import com.sk89q.worldedit.registry.state.IntegerProperty; +import com.sk89q.worldedit.registry.state.Property; +import com.sk89q.worldedit.util.Direction; +import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.util.concurrency.LazyReference; +import com.sk89q.worldedit.util.formatting.text.Component; +import com.sk89q.worldedit.world.RegenOptions; +import com.sk89q.worldedit.world.biome.BiomeType; +import com.sk89q.worldedit.world.block.BaseBlock; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockStateHolder; +import com.sk89q.worldedit.world.block.BlockType; +import com.sk89q.worldedit.world.block.BlockTypesCache; +import com.sk89q.worldedit.world.entity.EntityType; +import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; +import com.sk89q.worldedit.world.generation.StructureType; +import com.sk89q.worldedit.world.generation.TreeType; +import com.sk89q.worldedit.world.item.ItemType; +import com.sk89q.worldedit.world.registry.BlockMaterial; +import io.papermc.lib.PaperLib; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.SectionPos; +import net.minecraft.core.WritableRegistry; +import net.minecraft.core.component.DataComponentPatch; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.core.registries.Registries; +import net.minecraft.data.worldgen.features.AquaticFeatures; +import net.minecraft.data.worldgen.features.CaveFeatures; +import net.minecraft.data.worldgen.features.EndFeatures; +import net.minecraft.data.worldgen.features.NetherFeatures; +import net.minecraft.data.worldgen.features.PileFeatures; +import net.minecraft.data.worldgen.features.TreeFeatures; +import net.minecraft.data.worldgen.features.VegetationFeatures; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtOps; +import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; +import net.minecraft.resources.Identifier; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.dedicated.DedicatedServer; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.StringRepresentable; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; +import net.minecraft.world.level.levelgen.structure.BoundingBox; +import net.minecraft.world.level.levelgen.structure.Structure; +import net.minecraft.world.level.levelgen.structure.StructureStart; +import org.apache.logging.log4j.Logger; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.NamespacedKey; +import org.bukkit.World; +import org.bukkit.block.data.BlockData; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.block.CraftBlockState; +import org.bukkit.craftbukkit.block.data.CraftBlockData; +import org.bukkit.craftbukkit.entity.CraftEntity; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.craftbukkit.util.CraftNamespacedKey; +import org.bukkit.craftbukkit.util.TransformerLevelAccessor; +import org.bukkit.entity.Player; +import org.enginehub.linbus.tree.LinCompoundTag; + +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.OptionalInt; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1.PaperweightPlatformAdapter.createOutput; +import static net.minecraft.core.registries.Registries.BIOME; + +public final class PaperweightFaweAdapter extends FaweAdapter { + + private static final Logger LOGGER = LogManagerCompat.getLogger(); + private static Method CHUNK_HOLDER_WAS_ACCESSIBLE_SINCE_LAST_SAVE; + private static final Codec COMPONENTS_CODEC = DataComponentPatch.CODEC.optionalFieldOf( + "components", DataComponentPatch.EMPTY + ).codec(); + + static { + try { + CHUNK_HOLDER_WAS_ACCESSIBLE_SINCE_LAST_SAVE = ChunkHolder.class.getDeclaredMethod("wasAccessibleSinceLastSave"); + } catch (NoSuchMethodException ignored) { // may not be present in newer paper versions + } + } + + private final PaperweightMapChunkUtil mapUtil = new PaperweightMapChunkUtil(); + + public PaperweightFaweAdapter() throws NoSuchFieldException, NoSuchMethodException { + super(new PaperweightAdapter()); + } + + public Function blockEntityToCompoundTag() { + return blockEntity -> FaweCompoundTag.of( + () -> { + LinValueOutput output = createOutput(); + blockEntity.saveWithId(output); + return output.buildResult(); + } + ); + } + + public static Property adaptProperty(net.minecraft.world.level.block.state.properties.Property property) { + return switch (property) { + case net.minecraft.world.level.block.state.properties.BooleanProperty booleanProperty -> + new BooleanProperty(booleanProperty.getName(), ImmutableList.copyOf(booleanProperty.getPossibleValues())); + case net.minecraft.world.level.block.state.properties.IntegerProperty integerProperty -> + new IntegerProperty(integerProperty.getName(), ImmutableList.copyOf(integerProperty.getPossibleValues())); + case net.minecraft.world.level.block.state.properties.EnumProperty enumProperty -> { + if (enumProperty.getValueClass() == net.minecraft.core.Direction.class) { + yield new DirectionalProperty(enumProperty.getName(), enumProperty.getPossibleValues().stream() + .map(StringRepresentable::getSerializedName) + .map(s -> s.toUpperCase(Locale.ROOT)) + .map(Direction::valueOf) + .toList() + ); + } + yield new EnumProperty(enumProperty.getName(), enumProperty.getPossibleValues().stream() + .map(StringRepresentable::getSerializedName).collect(Collectors.toCollection(ArrayList::new))); + } + default -> throw new IllegalArgumentException("FastAsyncWorldEdit needs an update to support " + property.getClass().getSimpleName()); + }; + } + + private static String getEntityId(Entity entity) { + return net.minecraft.world.entity.EntityType.getKey(entity.getType()).toString(); + } + + @Override + public BukkitImplAdapter getParent() { + return parent; + } + + @Override + protected void ensureInit() { + if (!this.initialised) { + init(); + } + } + + private synchronized boolean init() { + if (ibdToOrdinal != null && ibdToOrdinal[1] != 0) { + return false; + } + ibdToOrdinal = new int[BlockTypesCache.states.length]; // size + ordinalToIbdID = new int[ibdToOrdinal.length]; // size + for (int i = 0; i < ibdToOrdinal.length; i++) { + BlockState blockState = BlockTypesCache.states[i]; + PaperweightBlockMaterial material = (PaperweightBlockMaterial) blockState.getMaterial(); + int id = Block.BLOCK_STATE_REGISTRY.getId(material.getState()); + char ordinal = blockState.getOrdinalChar(); + ibdToOrdinal[id] = ordinal; + ordinalToIbdID[ordinal] = id; + } + Map>> properties = new HashMap<>(); + try { + for (Field field : BlockStateProperties.class.getDeclaredFields()) { + Object obj = field.get(null); + if (!(obj instanceof net.minecraft.world.level.block.state.properties.Property state)) { + continue; + } + Property property = adaptProperty(state); + properties.compute(property.getName().toLowerCase(Locale.ROOT), (k, v) -> { + if (v == null) { + v = new ArrayList<>(Collections.singletonList(property)); + } else { + v.add(property); + } + return v; + }); + } + } catch (IllegalAccessException e) { + LOGGER.error("failed to initialize block states", e); + } finally { + allBlockProperties = ImmutableMap.copyOf(properties); + } + initialised = true; + return true; + } + + @Override + public Collection getRegisteredDefaultBlockStates() { + ArrayList states = new ArrayList<>(); + for (final Block block : BuiltInRegistries.BLOCK) { + states.add(block.defaultBlockState().asBlockData().getAsString()); + } + return states; + } + + @Override + public BlockMaterial getMaterial(BlockType blockType) { + Block block = getBlock(blockType); + return new PaperweightBlockMaterial(block); + } + + @Override + public synchronized BlockMaterial getMaterial(BlockState state) { + net.minecraft.world.level.block.state.BlockState blockState = ((CraftBlockData) Bukkit.createBlockData(state.getAsString())).getState(); + return new PaperweightBlockMaterial(blockState.getBlock(), blockState); + } + + public Block getBlock(BlockType blockType) { + return DedicatedServer.getServer().registryAccess().lookupOrThrow(Registries.BLOCK) + .getValue(Identifier.fromNamespaceAndPath(blockType.getNamespace(), blockType.getResource())); + } + + @Deprecated + @Override + public BlockState getBlock(Location location) { + Preconditions.checkNotNull(location); + + int x = location.getBlockX(); + int y = location.getBlockY(); + int z = location.getBlockZ(); + final ServerLevel handle = getServerLevel(location.getWorld()); + LevelChunk chunk = handle.getChunk(x >> 4, z >> 4); + final BlockPos blockPos = new BlockPos(x, y, z); + final net.minecraft.world.level.block.state.BlockState blockData = chunk.getBlockState(blockPos); + BlockState state = adapt(blockData); + if (state == null) { + org.bukkit.block.Block bukkitBlock = location.getBlock(); + state = BukkitAdapter.adapt(bukkitBlock.getBlockData()); + } + return state; + } + + @Override + public BaseBlock getFullBlock(final Location location) { + Preconditions.checkNotNull(location); + + int x = location.getBlockX(); + int y = location.getBlockY(); + int z = location.getBlockZ(); + + final ServerLevel handle = getServerLevel(location.getWorld()); + LevelChunk chunk = handle.getChunk(x >> 4, z >> 4); + final BlockPos blockPos = new BlockPos(x, y, z); + final net.minecraft.world.level.block.state.BlockState blockData = chunk.getBlockState(blockPos); + BlockState state = adapt(blockData); + if (state == null) { + org.bukkit.block.Block bukkitBlock = location.getBlock(); + state = BukkitAdapter.adapt(bukkitBlock.getBlockData()); + } + if (state.getBlockType().getMaterial().hasContainer()) { + // Read the NBT data + BlockEntity blockEntity = chunk.getBlockEntity(blockPos, LevelChunk.EntityCreationType.CHECK); + if (blockEntity != null) { + LinValueOutput output = createOutput(); + blockEntity.saveWithId(output); + return state.toBaseBlock(output.buildResult()); + } + } + + return state.toBaseBlock(); + } + + private static final Set SUPPORTED_SIDE_EFFECTS = Sets.immutableEnumSet( + SideEffect.HISTORY, + SideEffect.HEIGHTMAPS, + SideEffect.LIGHTING, + SideEffect.NEIGHBORS, + SideEffect.ENTITY_EVENTS + ); + + @Override + public Set getSupportedSideEffects() { + return SUPPORTED_SIDE_EFFECTS; + } + + @Override + public WorldNativeAccess createWorldNativeAccess(World world) { + return new PaperweightFaweWorldNativeAccess(this, new WeakReference<>(getServerLevel(world))); + } + + @Override + public BaseEntity getEntity(org.bukkit.entity.Entity entity) { + Preconditions.checkNotNull(entity); + + CraftEntity craftEntity = ((CraftEntity) entity); + Entity mcEntity = craftEntity.getHandle(); + + String id = getEntityId(mcEntity); + EntityType type = com.sk89q.worldedit.world.entity.EntityTypes.get(id); + Supplier saveTag = () -> { + final LinValueOutput output = createOutput(); + if (!mcEntity.save(output)) { + return null; + } + //add Id for AbstractChangeSet to work + return output.toBuilder().putString("Id", id).build(); + }; + return new LazyBaseEntity(type, saveTag); + } + + @Override + public Component getRichBlockName(BlockType blockType) { + return parent.getRichBlockName(blockType); + } + + @Override + public Component getRichItemName(ItemType itemType) { + return parent.getRichItemName(itemType); + } + + @Override + public Component getRichItemName(BaseItemStack itemStack) { + return parent.getRichItemName(itemStack); + } + + @Override + public OptionalInt getInternalBlockStateId(BlockState state) { + PaperweightBlockMaterial material = (PaperweightBlockMaterial) state.getMaterial(); + net.minecraft.world.level.block.state.BlockState mcState = material.getState(); + return OptionalInt.of(Block.BLOCK_STATE_REGISTRY.getId(mcState)); + } + + @Override + public BlockState adapt(BlockData blockData) { + CraftBlockData cbd = ((CraftBlockData) blockData); + net.minecraft.world.level.block.state.BlockState ibd = cbd.getState(); + return adapt(ibd); + } + + public BlockState adapt(net.minecraft.world.level.block.state.BlockState blockState) { + return BlockTypesCache.states[adaptToChar(blockState)]; + } + + public char adaptToChar(net.minecraft.world.level.block.state.BlockState blockState) { + int id = Block.BLOCK_STATE_REGISTRY.getId(blockState); + if (initialised) { + return (char) ibdToOrdinal[id]; + } + synchronized (this) { + if (initialised) { + return (char) ibdToOrdinal[id]; + } + try { + init(); + return (char) ibdToOrdinal[id]; + } catch (ArrayIndexOutOfBoundsException e1) { + LOGGER.error("Attempted to convert {} with ID {} to char. ibdToOrdinal length: {}. Defaulting to air!", + blockState.getBlock(), Block.BLOCK_STATE_REGISTRY.getId(blockState), ibdToOrdinal.length, e1 + ); + return BlockTypesCache.ReservedIDs.AIR; + } + } + } + + public char ibdIDToOrdinal(int id) { + if (initialised) { + return (char) ibdToOrdinal[id]; + } + synchronized (this) { + if (initialised) { + return (char) ibdToOrdinal[id]; + } + init(); + return (char) ibdToOrdinal[id]; + } + } + + @Override + public int[] getIbdToOrdinal() { + if (initialised) { + return ibdToOrdinal; + } + synchronized (this) { + if (initialised) { + return ibdToOrdinal; + } + init(); + return ibdToOrdinal; + } + } + + public int ordinalToIbdID(char ordinal) { + if (initialised) { + return ordinalToIbdID[ordinal]; + } + synchronized (this) { + if (initialised) { + return ordinalToIbdID[ordinal]; + } + init(); + return ordinalToIbdID[ordinal]; + } + } + + @Override + public int[] getOrdinalToIbdID() { + if (initialised) { + return ordinalToIbdID; + } + synchronized (this) { + if (initialised) { + return ordinalToIbdID; + } + init(); + return ordinalToIbdID; + } + } + + @Override + public > BlockData adapt(B state) { + PaperweightBlockMaterial material = (PaperweightBlockMaterial) state.getMaterial(); + return material.getBlockData(); + } + + public net.minecraft.world.level.block.state.BlockState adapt(BlockState blockState) { + return Block.stateById(getOrdinalToIbdID()[blockState.getOrdinal()]); + } + + @Override + public void sendFakeChunk(World world, Player player, ChunkPacket chunkPacket) { + ServerLevel nmsWorld = getServerLevel(world); + ChunkHolder map = PaperweightPlatformAdapter.getPlayerChunk(nmsWorld, chunkPacket.getChunkX(), chunkPacket.getChunkZ()); + if (map != null && wasAccessibleSinceLastSave(map)) { + boolean flag = false; + // PlayerChunk.d players = map.players; + Stream stream = /*players.a(new ChunkCoordIntPair(packet.getChunkX(), packet.getChunkZ()), flag) + */ Stream.empty(); + + ServerPlayer checkPlayer = player == null ? null : ((CraftPlayer) player).getHandle(); + stream.filter(entityPlayer -> checkPlayer == null || entityPlayer == checkPlayer) + .forEach(entityPlayer -> { + synchronized (chunkPacket) { + ClientboundLevelChunkWithLightPacket nmsPacket = (ClientboundLevelChunkWithLightPacket) chunkPacket.getNativePacket(); + if (nmsPacket == null) { + nmsPacket = mapUtil.create(this, chunkPacket); + chunkPacket.setNativePacket(nmsPacket); + } + try { + FaweCache.INSTANCE.CHUNK_FLAG.get().set(true); + entityPlayer.connection.send(nmsPacket); + } finally { + FaweCache.INSTANCE.CHUNK_FLAG.get().set(false); + } + } + }); + } + } + + @Override + public Map> getProperties(BlockType blockType) { + return getParent().getProperties(blockType); + } + + @Override + public boolean canPlaceAt(World world, BlockVector3 blockVector3, BlockState blockState) { + int internalId = BlockStateIdAccess.getBlockStateId(blockState); + net.minecraft.world.level.block.state.BlockState blockState1 = Block.stateById(internalId); + return blockState1.getPostProcessPos( + getServerLevel(world), + new BlockPos(blockVector3.x(), blockVector3.y(), blockVector3.z()) + ) != null; + } + + @Override + public org.bukkit.inventory.ItemStack adapt(BaseItemStack baseItemStack) { + final RegistryAccess.Frozen registryAccess = DedicatedServer.getServer().registryAccess(); + ItemStack stack = new ItemStack( + registryAccess.lookupOrThrow(Registries.ITEM).getValueOrThrow(ResourceKey.create( + Registries.ITEM, Identifier.parse(baseItemStack.getType().id()) + )), + baseItemStack.getAmount() + ); + final CompoundTag nbt = (CompoundTag) fromNativeLin(baseItemStack.getNbt()); + if (nbt != null) { + final DataComponentPatch patch = COMPONENTS_CODEC + .parse(registryAccess.createSerializationContext(NbtOps.INSTANCE), nbt) + .getOrThrow(); + stack.applyComponents(patch); + } + return CraftItemStack.asCraftMirror(stack); + } + + @Override + protected void preCaptureStates(final ServerLevel serverLevel) { + serverLevel.captureTreeGeneration = true; + serverLevel.captureBlockStates = true; + } + + @Override + protected List getCapturedBlockStatesCopy(final ServerLevel serverLevel) { + return new ArrayList<>(serverLevel.capturedBlockStates.values()); + } + + @Override + protected void postCaptureBlockStates(final ServerLevel serverLevel) { + serverLevel.captureBlockStates = false; + serverLevel.captureTreeGeneration = false; + serverLevel.capturedBlockStates.clear(); + } + + private T syncRegion(World world, BlockVector3 pt, Supplier supplier) { + if (FoliaUtil.isFoliaServer()) { + if (Bukkit.isOwnedByCurrentRegion(world, pt.x() >> 4, pt.z() >> 4)) { + return supplier.get(); + } + Location location = new Location(world, pt.x(), pt.y(), pt.z()); + CompletableFuture future = new CompletableFuture<>(); + Bukkit.getServer().getRegionScheduler().run( + WorldEditPlugin.getInstance(), + location, + scheduledTask -> { + try { + future.complete(supplier.get()); + } catch (Throwable throwable) { + future.completeExceptionally(throwable); + } + } + ); + return future.join(); + } + return TaskManager.taskManager().sync(supplier); + } + + @Override + public boolean generateFeature(ConfiguredFeatureType feature, World world, EditSession editSession, BlockVector3 pt) { + ServerLevel serverLevel = getServerLevel(world); + ChunkGenerator generator = serverLevel.getMinecraftWorld().getChunkSource().getGenerator(); + + ConfiguredFeature configuredFeature = serverLevel + .registryAccess() + .lookupOrThrow(Registries.CONFIGURED_FEATURE) + .getValue(Identifier.tryParse(feature.id())); + + com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1.FaweBlockStateListPopulator populator = new FaweBlockStateListPopulator(serverLevel); + List placed = syncRegion(world, pt, () -> { + preCaptureStates(serverLevel); + try { + if (!configuredFeature.place( + populator, + generator, + serverLevel.getRandom(), + new BlockPos(pt.x(), pt.y(), pt.z()) + )) { + return null; + } + List placedBlocks = new ArrayList<>(populator.getSnapshotBlocks()); + placedBlocks.addAll(serverLevel.capturedBlockStates.values()); + return placedBlocks; + } finally { + postCaptureBlockStates(serverLevel); + } + }); + + return placeFeatureIntoSession(editSession, populator, placed); + } + + @Override + public boolean generateStructure(StructureType type, World world, EditSession editSession, BlockVector3 pt) { + ServerLevel serverLevel = getServerLevel(world); + Registry structureRegistry = serverLevel.registryAccess().lookupOrThrow(Registries.STRUCTURE); + Structure structure = structureRegistry.getValue(Identifier.tryParse(type.id())); + if (structure == null) { + return false; + } + + ServerChunkCache chunkManager = serverLevel.getChunkSource(); + + ChunkPos chunkPos = ChunkPos.containing(new BlockPos(pt.x(), pt.y(), pt.z())); + TransformerLevelAccessor access = new TransformerLevelAccessor(); + FaweBlockStateListPopulator populator = new FaweBlockStateListPopulator(serverLevel); + access.setDelegate(populator); + List placed = syncRegion(world, pt, () -> { + preCaptureStates(serverLevel); + try { + StructureStart structureStart = structure.generate( + structureRegistry.wrapAsHolder(structure), + serverLevel.dimension(), + serverLevel.registryAccess(), + chunkManager.getGenerator(), + chunkManager.getGenerator().getBiomeSource(), + chunkManager.randomState(), + serverLevel.getStructureManager(), + serverLevel.getSeed(), + chunkPos, + 0, + populator, + biome -> true + ); + if (!structureStart.isValid()) { + return null; + } else { + BoundingBox boundingBox = structureStart.getBoundingBox(); + ChunkPos min = new ChunkPos( + SectionPos.blockToSectionCoord(boundingBox.minX()), + SectionPos.blockToSectionCoord(boundingBox.minZ()) + ); + ChunkPos max = new ChunkPos( + SectionPos.blockToSectionCoord(boundingBox.maxX()), + SectionPos.blockToSectionCoord(boundingBox.maxZ()) + ); + ChunkPos.rangeClosed(min, max).forEach((chunkPosx) -> structureStart.placeInChunk( + access, + serverLevel.structureManager(), + chunkManager.getGenerator(), + serverLevel.getRandom(), + new BoundingBox( + chunkPosx.getMinBlockX(), + serverLevel.getMinY(), + chunkPosx.getMinBlockZ(), + chunkPosx.getMaxBlockX(), + serverLevel.getMaxY(), chunkPosx.getMaxBlockZ() + ), chunkPosx + )); + List placedBlocks = new ArrayList<>(populator.getSnapshotBlocks()); + placedBlocks.addAll(serverLevel.capturedBlockStates.values()); + return placedBlocks; + } + } finally { + postCaptureBlockStates(serverLevel); + } + }); + + return placeFeatureIntoSession(editSession, populator, placed); + } + + @Override + public boolean generateTree( + final TreeType treeType, + final World world, + final EditSession session, + final BlockVector3 pt + ) throws MaxChangedBlocksException { + ServerLevel serverLevel = getServerLevel(world); + ChunkGenerator generator = serverLevel.getMinecraftWorld().getChunkSource().getGenerator(); + + PlacedFeature placedFeature = serverLevel + .registryAccess() + .lookupOrThrow(Registries.PLACED_FEATURE) + .getValue(Identifier.tryParse(treeType.id())); + + FaweBlockStateListPopulator populator = new FaweBlockStateListPopulator(serverLevel); + List placed = syncRegion(world, pt, () -> { + preCaptureStates(serverLevel); + try { + if (!placedFeature.place( + populator, + generator, + serverLevel.getRandom(), + new BlockPos(pt.x(), pt.y(), pt.z()) + )) { + return null; + } + List placedBlocks = new ArrayList<>(populator.getSnapshotBlocks()); + placedBlocks.addAll(serverLevel.capturedBlockStates.values()); + return placedBlocks; + } finally { + postCaptureBlockStates(serverLevel); + } + }); + + return placeFeatureIntoSession(session, populator, placed); + } + + private boolean placeFeatureIntoSession( + final EditSession editSession, + final FaweBlockStateListPopulator populator, + final List placed + ) { + if (placed == null || placed.isEmpty()) { + return false; + } + + for (CraftBlockState craftBlockState : placed) { + if (craftBlockState == null) { + continue; + } + BlockPos pos = craftBlockState.getPosition(); + editSession.setBlock(pos.getX(), pos.getY(), pos.getZ(), BukkitAdapter.adapt(craftBlockState.getBlockData())); + BlockEntity blockEntity = populator.getBlockEntity(pos); + if (blockEntity != null) { + LinValueOutput output = createOutput(); + blockEntity.saveWithId(output); + editSession.tile(pos.getX(), pos.getY(), pos.getZ(), FaweCompoundTag.of(output::buildResult)); + } + } + return true; + } + + @Override + public void setupFeatures() { + DedicatedServer server = ((CraftServer) Bukkit.getServer()).getServer(); + + // All these features should be the "face" selected + Set face_features = Arrays + .stream(new Class[]{AquaticFeatures.class, PileFeatures.class, TreeFeatures.class, VegetationFeatures.class}) + .flatMap(c -> Arrays.stream(c.getFields())) + .filter(f -> { + int modifiers = f.getModifiers(); + return Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers); + }) + .filter(f -> f.getType().equals(ResourceKey.class)) + .map(f -> { + try { + Object val = f.get(null); + return val; + } catch (IllegalAccessException e) { + LOGGER.error(e); + return null; + } + }) + .filter(Objects::nonNull) + .map(o -> (ResourceKey) o) + .map(k -> k.identifier().toString()) + .collect(Collectors.toCollection(java.util.HashSet::new)); + face_features.add(CaveFeatures.DRIPSTONE_CLUSTER.identifier().toString()); + face_features.add(CaveFeatures.LARGE_DRIPSTONE.identifier().toString()); + face_features.add(CaveFeatures.POINTED_DRIPSTONE.identifier().toString()); + face_features.add(CaveFeatures.GLOW_LICHEN.identifier().toString()); + face_features.add(CaveFeatures.CAVE_VINE.identifier().toString()); + face_features.add(CaveFeatures.CAVE_VINE_IN_MOSS.identifier().toString()); + face_features.add(CaveFeatures.MOSS_VEGETATION.identifier().toString()); + face_features.add(CaveFeatures.DRIPLEAF.identifier().toString()); + face_features.add(EndFeatures.CHORUS_PLANT.identifier().toString()); + face_features.add(EndFeatures.END_PLATFORM.identifier().toString()); + face_features.add(NetherFeatures.SMALL_BASALT_COLUMNS.identifier().toString()); + face_features.add(NetherFeatures.LARGE_BASALT_COLUMNS.identifier().toString()); + face_features.add(NetherFeatures.GLOWSTONE_EXTRA.identifier().toString()); + + // Features + for (Identifier name : server.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).keySet()) { + String id = name.toString(); + if (ConfiguredFeatureType.REGISTRY.get(id) == null) { + ConfiguredFeatureType.REGISTRY.register(id, new ConfiguredFeatureType(id, face_features.contains(id))); + } + } + + // Structures + for (Identifier name : server.registryAccess().lookupOrThrow(Registries.STRUCTURE).keySet()) { + if (StructureType.REGISTRY.get(name.toString()) == null) { + StructureType.REGISTRY.register(name.toString(), new StructureType(name.toString())); + } + } + } + + @Override + protected ServerLevel getServerLevel(final World world) { + return ((CraftWorld) world).getHandle(); + } + + @Override + public BaseItemStack adapt(org.bukkit.inventory.ItemStack itemStack) { + final RegistryAccess.Frozen registryAccess = DedicatedServer.getServer().registryAccess(); + final ItemStack nmsStack = CraftItemStack.asNMSCopy(itemStack); + // We should be fine to perform this later as we're using a deep-copied itemstack (above) + final Supplier tag = () -> COMPONENTS_CODEC.encodeStart( + registryAccess.createSerializationContext(NbtOps.INSTANCE), + nmsStack.getComponentsPatch() + ).getOrThrow(); + return new BaseItemStack( + BukkitAdapter.asItemType(itemStack.getType()), + LazyReference.from(() -> (LinCompoundTag) toNativeLin(tag.get())), + itemStack.getAmount() + ); + } + + @Override + public Tag toNative(net.minecraft.nbt.Tag foreign) { + return parent.toNative(foreign); + } + + @Override + public net.minecraft.nbt.Tag fromNative(Tag foreign) { + return parent.fromNative(foreign); + } + + @Override + public boolean regenerate(World bukkitWorld, Region region, Extent target, RegenOptions options) throws Exception { + return new PaperweightRegen(bukkitWorld, region, target, options).regenerate(); + } + + @Override + public IChunkGet get(World world, int chunkX, int chunkZ) { + return new PaperweightGetBlocks(world, chunkX, chunkZ); + } + + @Override + public int getInternalBiomeId(BiomeType biomeType) { + final Registry registry = MinecraftServer + .getServer() + .registryAccess() + .lookupOrThrow(BIOME); + Identifier resourceLocation = Identifier.tryParse(biomeType.id()); + Biome biome = registry.getValue(resourceLocation); + return registry.getId(biome); + } + + @Override + public Iterable getRegisteredBiomes() { + WritableRegistry biomeRegistry = (WritableRegistry) ((CraftServer) Bukkit.getServer()) + .getServer() + .registryAccess() + .lookupOrThrow(BIOME); + List keys = biomeRegistry.stream() + .map(biomeRegistry::getKey).filter(Objects::nonNull).toList(); + List namespacedKeys = new ArrayList<>(); + for (Identifier key : keys) { + try { + namespacedKeys.add(CraftNamespacedKey.fromMinecraft(key)); + } catch (IllegalArgumentException e) { + LOGGER.error("Error converting biome key {}", key.toString(), e); + } + } + return namespacedKeys; + } + + @Override + public RelighterFactory getRelighterFactory() { + if (PaperLib.isPaper()) { + return new PaperweightStarlightRelighterFactory(); + } else { + return new NMSRelighterFactory(); + } + } + + @Override + public Map>> getAllProperties() { + if (initialised) { + return allBlockProperties; + } + synchronized (this) { + if (initialised) { + return allBlockProperties; + } + init(); + return allBlockProperties; + } + } + + @Override + public IBatchProcessor getTickingPostProcessor() { + return new PaperweightPostProcessor(); + } + + @Override + public PlacementStateProcessor getPlatformPlacementProcessor(Extent extent, BlockTypeMask mask, Region region) { + return new PaperweightPlacementStateProcessor(extent, mask, region); + } + + private boolean wasAccessibleSinceLastSave(ChunkHolder holder) { + if (PaperLib.isPaper()) { // Papers new chunk system has no related replacement - therefor we assume true. + return true; + } + try { + return (boolean) CHUNK_HOLDER_WAS_ACCESSIBLE_SINCE_LAST_SAVE.invoke(holder); + } catch (IllegalAccessException | InvocationTargetException ignored) { + return false; + } + } + +} diff --git a/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightFaweMutableBlockPlaceContext.java b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightFaweMutableBlockPlaceContext.java new file mode 100644 index 0000000000..81389a9b6f --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightFaweMutableBlockPlaceContext.java @@ -0,0 +1,142 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.Vec3; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PaperweightFaweMutableBlockPlaceContext extends BlockPlaceContext { + + private static final BlockHitResult DEFAULT_BLOCK_HIT = new BlockHitResult( + new Vec3(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE), + Direction.NORTH, + new BlockPos(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE), + false + ); + private final ServerLevel level; + private BlockHitResult hitResult = null; + private Direction direction = null; + private BlockPos relativePos; + + @SuppressWarnings("DataFlowIssue") + public PaperweightFaweMutableBlockPlaceContext(ServerLevel level) { + super( + level, + null, + null, + null, + DEFAULT_BLOCK_HIT + + ); + this.level = level; + this.replaceClicked = false; + } + + public PaperweightFaweMutableBlockPlaceContext withSetting(BlockHitResult hitResult, Direction direction) { + this.hitResult = hitResult; + this.direction = direction; + this.relativePos = hitResult.getBlockPos().relative(hitResult.getDirection()); + return this; + } + + @Override + @Nonnull + public BlockPos getClickedPos() { + return this.relativePos; + } + + @Override + @Nonnull + public Direction getClickedFace() { + return this.hitResult.getDirection(); + } + + @Override + @Nonnull + public Vec3 getClickLocation() { + return this.hitResult.getLocation(); + } + + @Override + public boolean isInside() { + return this.hitResult.isInside(); + } + + @Override + @SuppressWarnings("NullableProblems") + public ItemStack getItemInHand() { + return ItemStack.EMPTY; + } + + @Nullable + @Override + public Player getPlayer() { + return null; + } + + @Override + @SuppressWarnings("NullableProblems") + public InteractionHand getHand() { + return null; + } + + @Override + @Nonnull + public Level getLevel() { + return this.level; + } + + @Override + @Nonnull + public Direction getHorizontalDirection() { + return this.direction.getAxis() == Direction.Axis.Y ? Direction.NORTH : this.direction; + } + + @Override + public boolean isSecondaryUseActive() { + return false; + } + + @Override + public float getRotation() { + return (float) (this.direction.get2DDataValue() * 90); + } + + @Override + public boolean canPlace() { + return this.getLevel().getBlockState(this.getClickedPos()).canBeReplaced(this); + } + + @Override + public boolean replacingClickedOnBlock() { + return false; + } + + @Override + @Nonnull + public Direction getNearestLookingDirection() { + return direction; + } + + @Override + @Nonnull + public Direction getNearestLookingVerticalDirection() { + return direction.getAxis() == Direction.Axis.Y ? Direction.UP : Direction.DOWN; + } + + @Override + @Nonnull + public Direction[] getNearestLookingDirections() { + return new Direction[]{direction}; + } + +} diff --git a/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightFaweWorldNativeAccess.java b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightFaweWorldNativeAccess.java new file mode 100644 index 0000000000..c70d035f6e --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightFaweWorldNativeAccess.java @@ -0,0 +1,310 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1; + +import com.fastasyncworldedit.core.Fawe; +import com.fastasyncworldedit.core.math.IntPair; +import com.fastasyncworldedit.core.util.TaskManager; +import com.fastasyncworldedit.core.util.task.RunnableVal; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.internal.block.BlockStateIdAccess; +import com.sk89q.worldedit.internal.wna.WorldNativeAccess; +import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.util.SideEffectSet; +import com.sk89q.worldedit.world.block.BlockState; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.FullChunkStatus; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.redstone.ExperimentalRedstoneUtils; +import net.minecraft.world.level.storage.ValueInput; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.block.data.CraftBlockData; +import org.bukkit.event.block.BlockPhysicsEvent; +import org.enginehub.linbus.tree.LinCompoundTag; + +import javax.annotation.Nullable; +import java.lang.ref.WeakReference; +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1.PaperweightPlatformAdapter.createInput; + +public class PaperweightFaweWorldNativeAccess implements WorldNativeAccess { + + private static final int UPDATE = 1; + private static final int NOTIFY = 2; + private static final Direction[] NEIGHBOUR_ORDER = { + Direction.EAST, + Direction.WEST, + Direction.DOWN, + Direction.UP, + Direction.NORTH, + Direction.SOUTH + }; + private final PaperweightFaweAdapter paperweightFaweAdapter; + private final WeakReference level; + private final AtomicInteger lastTick; + private final Set cachedChanges = new HashSet<>(); + private final Set cachedChunksToSend = new HashSet<>(); + private SideEffectSet sideEffectSet; + + public PaperweightFaweWorldNativeAccess(PaperweightFaweAdapter paperweightFaweAdapter, WeakReference level) { + this.paperweightFaweAdapter = paperweightFaweAdapter; + this.level = level; + // Use the actual tick as minecraft-defined so we don't try to force blocks into the world when the server's already lagging. + // - With the caveat that we don't want to have too many cached changed (1024) so we'd flush those at 1024 anyway. + this.lastTick = new AtomicInteger(getCurrentTick()); + } + + private Level getLevel() { + return Objects.requireNonNull(level.get(), "The reference to the world was lost"); + } + + @Override + public void setCurrentSideEffectSet(SideEffectSet sideEffectSet) { + this.sideEffectSet = sideEffectSet; + } + + @Override + public LevelChunk getChunk(int x, int z) { + return getLevel().getChunk(x, z); + } + + @Override + public net.minecraft.world.level.block.state.BlockState toNative(BlockState blockState) { + int stateId = paperweightFaweAdapter.ordinalToIbdID(blockState.getOrdinalChar()); + return BlockStateIdAccess.isValidInternalId(stateId) + ? Block.stateById(stateId) + : ((CraftBlockData) BukkitAdapter.adapt(blockState)).getState(); + } + + @Override + public net.minecraft.world.level.block.state.BlockState getBlockState(LevelChunk levelChunk, BlockPos blockPos) { + return levelChunk.getBlockState(blockPos); + } + + @Nullable + @Override + public synchronized net.minecraft.world.level.block.state.BlockState setBlockState( + LevelChunk levelChunk, BlockPos blockPos, + net.minecraft.world.level.block.state.BlockState blockState + ) { + int currentTick = getCurrentTick(); + if (Fawe.isMainThread()) { + return levelChunk.setBlockState(blockPos, blockState, + this.sideEffectSet.shouldApply(SideEffect.UPDATE) ? 0 : 512 + ); + } + // Since FAWE is.. Async we need to do it on the main thread (wooooo.. :( ) + cachedChanges.add(new CachedChange(levelChunk, blockPos, blockState)); + cachedChunksToSend.add(new IntPair(levelChunk.locX, levelChunk.locZ)); + boolean nextTick = lastTick.get() > currentTick; + if (nextTick || cachedChanges.size() >= 1024) { + if (nextTick) { + lastTick.set(currentTick); + } + flushAsync(nextTick); + } + return blockState; + } + + @Override + public net.minecraft.world.level.block.state.BlockState getValidBlockForPosition( + net.minecraft.world.level.block.state.BlockState blockState, + BlockPos blockPos + ) { + return Block.updateFromNeighbourShapes(blockState, getLevel(), blockPos); + } + + @Override + public BlockPos getPosition(int x, int y, int z) { + return new BlockPos(x, y, z); + } + + @Override + public void updateLightingForBlock(BlockPos blockPos) { + getLevel().getChunkSource().getLightEngine().checkBlock(blockPos); + } + + @Override + public boolean updateTileEntity(BlockPos blockPos, LinCompoundTag tag) { + // We will assume that the tile entity was created for us, + // though we do not do this on the other versions + BlockEntity blockEntity = getLevel().getBlockEntity(blockPos); + if (blockEntity == null) { + return false; + } + ValueInput input = createInput(tag); + blockEntity.loadWithComponents(input); + return true; + } + + + @Override + public void notifyBlockUpdate( + LevelChunk levelChunk, BlockPos blockPos, + net.minecraft.world.level.block.state.BlockState oldState, + net.minecraft.world.level.block.state.BlockState newState + ) { + if (levelChunk.getSections()[level.get().getSectionIndex(blockPos.getY())] != null) { + getLevel().sendBlockUpdated(blockPos, oldState, newState, UPDATE | NOTIFY); + } + } + + @Override + public boolean isChunkTicking(LevelChunk levelChunk) { + return levelChunk.getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING); + } + + @Override + public void markBlockChanged(LevelChunk levelChunk, BlockPos blockPos) { + if (levelChunk.getSections()[level.get().getSectionIndex(blockPos.getY())] != null) { + ((ServerChunkCache) getLevel().getChunkSource()).blockChanged(blockPos); + } + } + + @Override + public void notifyNeighbors( + BlockPos blockPos, + net.minecraft.world.level.block.state.BlockState oldState, + net.minecraft.world.level.block.state.BlockState newState + ) { + Level level = getLevel(); + if (sideEffectSet.shouldApply(SideEffect.EVENTS)) { + level.updateNeighborsAt(blockPos, oldState.getBlock()); + } else { + // When we don't want events, manually run the physics without them. + // Un-nest neighbour updating + for (Direction direction : NEIGHBOUR_ORDER) { + BlockPos shifted = blockPos.relative(direction); + level.getBlockState(shifted).handleNeighborChanged(level, shifted, oldState.getBlock(), ExperimentalRedstoneUtils.initialOrientation(level, null, null), false); + } + } + if (newState.hasAnalogOutputSignal()) { + level.updateNeighbourForOutputSignal(blockPos, newState.getBlock()); + } + } + + @Override + public void updateNeighbors( + BlockPos blockPos, + net.minecraft.world.level.block.state.BlockState oldState, + net.minecraft.world.level.block.state.BlockState newState, + int recursionLimit + ) { + Level level = getLevel(); + // a == updateNeighbors + // b == updateDiagonalNeighbors + oldState.updateIndirectNeighbourShapes(level, blockPos, NOTIFY, recursionLimit); + if (sideEffectSet.shouldApply(SideEffect.EVENTS)) { + CraftWorld craftWorld = level.getWorld(); + if (craftWorld != null) { + BlockPhysicsEvent event = new BlockPhysicsEvent( + craftWorld.getBlockAt(blockPos.getX(), blockPos.getY(), blockPos.getZ()), + newState.asBlockData() + ); + level.getCraftServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + return; + } + } + } + newState.triggerEvent(level, blockPos, NOTIFY, recursionLimit); + newState.updateIndirectNeighbourShapes(level, blockPos, NOTIFY, recursionLimit); + } + + @Override + public void updateBlock(BlockPos pos, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState) { + Level world = getLevel(); + newState.onPlace(world, pos, oldState, false); + } + + @Override + public void onBlockStateChange( + BlockPos blockPos, + net.minecraft.world.level.block.state.BlockState oldState, + net.minecraft.world.level.block.state.BlockState newState + ) { + getLevel().updatePOIOnBlockStateChange(blockPos, oldState, newState); + } + + private synchronized void flushAsync(final boolean sendChunks) { + final Set changes = Set.copyOf(cachedChanges); + cachedChanges.clear(); + final Set toSend; + if (sendChunks) { + toSend = Set.copyOf(cachedChunksToSend); + cachedChunksToSend.clear(); + } else { + toSend = Collections.emptySet(); + } + RunnableVal runnableVal = new RunnableVal<>() { + @Override + public void run(Object value) { + changes.forEach(cc -> cc.levelChunk.setBlockState(cc.blockPos, cc.blockState, + sideEffectSet.shouldApply(SideEffect.UPDATE) ? 0 : 512 + )); + if (!sendChunks) { + return; + } + for (IntPair chunk : toSend) { + PaperweightPlatformAdapter.sendChunk(chunk, getLevel().getWorld().getHandle(), chunk.x(), chunk.z()); + } + } + }; + TaskManager.taskManager().async(() -> TaskManager.taskManager().sync(runnableVal)); + } + + @Override + public synchronized void flush() { + RunnableVal runnableVal = new RunnableVal<>() { + @Override + public void run(Object value) { + cachedChanges.forEach(cc -> cc.levelChunk.setBlockState(cc.blockPos, cc.blockState, + sideEffectSet.shouldApply(SideEffect.UPDATE) ? 0 : 512 + )); + for (IntPair chunk : cachedChunksToSend) { + PaperweightPlatformAdapter.sendChunk(chunk, getLevel().getWorld().getHandle(), chunk.x(), chunk.z()); + } + } + }; + if (Fawe.isMainThread()) { + runnableVal.run(); + } else { + TaskManager.taskManager().sync(runnableVal); + } + cachedChanges.clear(); + cachedChunksToSend.clear(); + } + + private record CachedChange( + LevelChunk levelChunk, + BlockPos blockPos, + net.minecraft.world.level.block.state.BlockState blockState + ) { + + } + + private int getCurrentTick() { + try { + return MinecraftServer.currentTick; + } catch (NoSuchFieldError e) { + try { + return Bukkit.getCurrentTick(); + } catch (Exception ignored) { + return 0; + } + } + } + +} diff --git a/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightGetBlocks.java b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightGetBlocks.java new file mode 100644 index 0000000000..86b45ce9db --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightGetBlocks.java @@ -0,0 +1,1134 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1; + +import com.fastasyncworldedit.bukkit.adapter.AbstractBukkitGetBlocks; +import com.fastasyncworldedit.bukkit.adapter.DelegateSemaphore; +import com.fastasyncworldedit.bukkit.adapter.NativeEntityFunctionSet; +import com.fastasyncworldedit.core.FaweCache; +import com.fastasyncworldedit.core.configuration.Settings; +import com.fastasyncworldedit.core.extent.processor.heightmap.HeightMapType; +import com.fastasyncworldedit.core.internal.exception.FaweException; +import com.fastasyncworldedit.core.math.BitArrayUnstretched; +import com.fastasyncworldedit.core.math.IntPair; +import com.fastasyncworldedit.core.nbt.FaweCompoundTag; +import com.fastasyncworldedit.core.queue.IChunkSet; +import com.fastasyncworldedit.core.util.FoliaUtil; +import com.fastasyncworldedit.core.util.MathMan; +import com.fastasyncworldedit.core.util.NbtUtils; +import com.fastasyncworldedit.core.util.collection.AdaptedMap; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.BukkitEntity; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1.PaperweightFaweAdapter; +import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1.PaperweightGetBlocks_Copy; +import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1.PaperweightPlatformAdapter; +import com.sk89q.worldedit.internal.Constants; +import com.sk89q.worldedit.internal.util.LogManagerCompat; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.util.formatting.text.TextComponent; +import com.sk89q.worldedit.world.biome.BiomeType; +import com.sk89q.worldedit.world.block.BlockTypesCache; +import io.papermc.lib.PaperLib; +import io.papermc.paper.event.block.BeaconDeactivatedEvent; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.IdMap; +import net.minecraft.core.Registry; +import net.minecraft.core.SectionPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.util.BitStorage; +import net.minecraft.util.ZeroBitStorage; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntitySpawnReason; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.level.LightLayer; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.entity.BeaconBlockEntity; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.DataLayer; +import net.minecraft.world.level.chunk.HashMapPalette; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.LinearPalette; +import net.minecraft.world.level.chunk.Palette; +import net.minecraft.world.level.chunk.PalettedContainer; +import net.minecraft.world.level.chunk.PalettedContainerRO; +import net.minecraft.world.level.levelgen.Heightmap; +import net.minecraft.world.level.lighting.LevelLightEngine; +import net.minecraft.world.level.storage.ValueInput; +import org.apache.logging.log4j.Logger; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.block.CraftBlock; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.enginehub.linbus.tree.LinCompoundTag; +import org.enginehub.linbus.tree.LinDoubleTag; +import org.enginehub.linbus.tree.LinFloatTag; +import org.enginehub.linbus.tree.LinListTag; +import org.enginehub.linbus.tree.LinStringTag; +import org.enginehub.linbus.tree.LinTagType; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.Semaphore; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Function; + +import static com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1.PaperweightPlatformAdapter.createInput; +import static com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1.PaperweightPlatformAdapter.createOutput; +import static net.minecraft.core.registries.Registries.BIOME; + +public class PaperweightGetBlocks extends AbstractBukkitGetBlocks { + + private static final Logger LOGGER = LogManagerCompat.getLogger(); + + private static final Function posNms2We = v -> BlockVector3.at(v.getX(), v.getY(), v.getZ()); + public static final Function NMS_TO_TILE = ((PaperweightFaweAdapter) WorldEditPlugin + .getInstance() + .getBukkitImplAdapter()).blockEntityToCompoundTag(); + private final PaperweightFaweAdapter adapter = ((PaperweightFaweAdapter) WorldEditPlugin + .getInstance() + .getBukkitImplAdapter()); + private final ReadWriteLock sectionLock = new ReentrantReadWriteLock(); + private final Registry biomeRegistry; + private final IdMap> biomeHolderIdMap; + private final Object sendLock = new Object(); + private LevelChunk levelChunk; + private LevelChunkSection[] sections; + private DataLayer[] blockLight; + private DataLayer[] skyLight; + private boolean lightUpdate = false; + + public PaperweightGetBlocks(World world, int chunkX, int chunkZ) { + this(((CraftWorld) world).getHandle(), chunkX, chunkZ); + } + + public PaperweightGetBlocks(ServerLevel serverLevel, int chunkX, int chunkZ) { + super(serverLevel, chunkX, chunkZ, serverLevel.getMinY(), serverLevel.getMaxY() - 1); + this.skyLight = new DataLayer[getSectionCount()]; + this.blockLight = new DataLayer[getSectionCount()]; + this.biomeRegistry = serverLevel.registryAccess().lookupOrThrow(BIOME); + this.biomeHolderIdMap = biomeRegistry.asHolderIdMap(); + } + + @Override + public void setLightingToGet(char[][] light, int minSectionPosition, int maxSectionPosition) { + if (light != null) { + lightUpdate = true; + try { + fillLightNibble(light, LightLayer.BLOCK, minSectionPosition, maxSectionPosition); + } catch (Throwable e) { + LOGGER.error("Error setting lighting to get", e); + } + } + } + + @Override + public void setSkyLightingToGet(char[][] light, int minSectionPosition, int maxSectionPosition) { + if (light != null) { + lightUpdate = true; + try { + fillLightNibble(light, LightLayer.SKY, minSectionPosition, maxSectionPosition); + } catch (Throwable e) { + LOGGER.error("Error setting sky lighting to get", e); + } + } + } + + @Override + public void setHeightmapToGet(HeightMapType type, int[] data) { + // height + 1 to match server internal + BitArrayUnstretched bitArray = new BitArrayUnstretched(MathMan.log2nlz(getChunk().getHeight() + 1), 256); + bitArray.fromRaw(data); + Heightmap.Types nativeType = Heightmap.Types.valueOf(type.name()); + Heightmap heightMap = getChunk().heightmaps.get(nativeType); + heightMap.setRawData(getChunk(), nativeType, bitArray.getData()); + } + + @Override + public BiomeType getBiomeType(int x, int y, int z) { + LevelChunkSection section = getSections(false)[(y >> 4) - getMinSectionPosition()]; + Holder biomes = section.getNoiseBiome(x >> 2, (y & 15) >> 2, z >> 2); + return PaperweightPlatformAdapter.adapt(biomes, serverLevel); + } + + @Override + public void removeSectionLighting(int layer, boolean sky) { + SectionPos sectionPos = SectionPos.of(getChunk().getPos(), layer); + DataLayer dataLayer = serverLevel.getChunkSource().getLightEngine().getLayerListener(LightLayer.BLOCK).getDataLayerData( + sectionPos); + if (dataLayer != null) { + lightUpdate = true; + synchronized (dataLayer) { + byte[] bytes = dataLayer.getData(); + Arrays.fill(bytes, (byte) 0); + } + } + if (sky) { + SectionPos sectionPos1 = SectionPos.of(getChunk().getPos(), layer); + DataLayer dataLayer1 = serverLevel + .getChunkSource() + .getLightEngine() + .getLayerListener(LightLayer.SKY) + .getDataLayerData(sectionPos1); + if (dataLayer1 != null) { + lightUpdate = true; + synchronized (dataLayer1) { + byte[] bytes = dataLayer1.getData(); + Arrays.fill(bytes, (byte) 0); + } + } + } + } + + @Override + public FaweCompoundTag tile(final int x, final int y, final int z) { + LevelChunk chunk = getChunk(); + if (chunk == null) { + return null; + } + + BlockPos pos = new BlockPos((x & 15) + (chunkX << 4), y, (z & 15) + (chunkZ << 4)); + Map tiles = chunk.getBlockEntities(); + if (tiles == null) { + return null; + } + + BlockEntity blockEntity = tiles.get(pos); + if (blockEntity == null) { + return null; + } + + return NMS_TO_TILE.apply(blockEntity); + } + + @Override + public Map tiles() { + LevelChunk chunk = getChunk(); + if (chunk == null) { + return Collections.emptyMap(); + } + + Map tiles = chunk.getBlockEntities(); + if (tiles == null || tiles.isEmpty()) { + return Collections.emptyMap(); + } + + return AdaptedMap.immutable(tiles, posNms2We, NMS_TO_TILE); + } + + @Override + public int getSkyLight(int x, int y, int z) { + int layer = y >> 4; + int alayer = layer - getMinSectionPosition(); + if (skyLight[alayer] == null) { + SectionPos sectionPos = SectionPos.of(getChunk().getPos(), layer); + DataLayer dataLayer = + serverLevel.getChunkSource().getLightEngine().getLayerListener(LightLayer.SKY).getDataLayerData(sectionPos); + // If the server hasn't generated the section's NibbleArray yet, it will be null + if (dataLayer == null) { + byte[] LAYER_COUNT = new byte[2048]; + // Safe enough to assume if it's not created, it's under the sky. Unlikely to be created before lighting is fixed anyway. + Arrays.fill(LAYER_COUNT, (byte) 15); + dataLayer = new DataLayer(LAYER_COUNT); + ((LevelLightEngine) serverLevel.getChunkSource().getLightEngine()).queueSectionData( + LightLayer.BLOCK, + sectionPos, + dataLayer + ); + } + skyLight[alayer] = dataLayer; + } + return skyLight[alayer].get(x & 15, y & 15, z & 15); + } + + @Override + public int getEmittedLight(int x, int y, int z) { + int layer = y >> 4; + int alayer = layer - getMinSectionPosition(); + if (blockLight[alayer] == null) { + serverLevel.getRawBrightness(new BlockPos(1, 1, 1), 5); + SectionPos sectionPos = SectionPos.of(getChunk().getPos(), layer); + DataLayer dataLayer = serverLevel + .getChunkSource() + .getLightEngine() + .getLayerListener(LightLayer.BLOCK) + .getDataLayerData(sectionPos); + // If the server hasn't generated the section's DataLayer yet, it will be null + if (dataLayer == null) { + byte[] LAYER_COUNT = new byte[2048]; + // Safe enough to assume if it's not created, it's under the sky. Unlikely to be created before lighting is fixed anyway. + Arrays.fill(LAYER_COUNT, (byte) 15); + dataLayer = new DataLayer(LAYER_COUNT); + ((LevelLightEngine) serverLevel.getChunkSource().getLightEngine()).queueSectionData(LightLayer.BLOCK, sectionPos, + dataLayer + ); + } + blockLight[alayer] = dataLayer; + } + return blockLight[alayer].get(x & 15, y & 15, z & 15); + } + + @Override + public int[] getHeightMap(HeightMapType type) { + long[] longArray = getChunk().heightmaps.get(Heightmap.Types.valueOf(type.name())).getRawData(); + BitArrayUnstretched bitArray = new BitArrayUnstretched(9, 256, longArray); + return bitArray.toRaw(new int[256]); + } + + @Override + public @Nullable FaweCompoundTag entity(final UUID uuid) { + List entities = PaperweightPlatformAdapter.getEntities(getChunk()); + Entity entity = null; + for (Entity e : entities) { + if (e.getUUID().equals(uuid)) { + entity = e; + break; + } + } + if (entity != null) { + org.bukkit.entity.Entity bukkitEnt = entity.getBukkitEntity(); + return FaweCompoundTag.of(BukkitAdapter.adapt(bukkitEnt).getState().getNbt()); + } + for (FaweCompoundTag tag : entities()) { + if (uuid.equals(NbtUtils.uuid(tag))) { + return tag; + } + } + return null; + } + + @Override + public Collection entities() { + List entities = PaperweightPlatformAdapter.getEntities(getChunk()); + if (entities.isEmpty()) { + return Collections.emptyList(); + } + return new NativeEntityFunctionSet<>(entities, Entity::getUUID, e -> { + LinValueOutput output = createOutput(); + e.save(output); + return FaweCompoundTag.of(output::buildResult); + }); + } + + @Override + public Set getFullEntities() { + List entities = PaperweightPlatformAdapter.getEntities(getChunk()); + if (entities.isEmpty()) { + return Collections.emptySet(); + } + return new NativeEntityFunctionSet<>(entities, Entity::getUUID, e -> new BukkitEntity(e.getBukkitEntity())); + } + + private void removeEntity(Entity entity) { + entity.discard(); + } + + @Override + public CompletableFuture ensureLoaded(ServerLevel nmsWorld) { + return PaperweightPlatformAdapter.ensureLoaded(nmsWorld, chunkX, chunkZ); + } + + @Override + protected > T internalCall( + IChunkSet set, + Runnable finalizer, + int copyKey, + LevelChunk nmsChunk, + ServerLevel nmsWorld + ) throws Exception { + PaperweightGetBlocks_Copy copy = createCopy ? new PaperweightGetBlocks_Copy(nmsChunk) : null; + if (createCopy) { + if (copies.containsKey(copyKey)) { + throw new IllegalStateException("Copy key already used."); + } + copies.put(copyKey, copy); + } + // Remove existing tiles. Create a copy so that we can remove blocks + Map chunkTiles = new HashMap<>(nmsChunk.getBlockEntities()); + List beaconPositions = null; + if (!chunkTiles.isEmpty()) { + for (Map.Entry entry : chunkTiles.entrySet()) { + final BlockPos pos = entry.getKey(); + final int lx = pos.getX() & 15; + final int ly = pos.getY(); + final int lz = pos.getZ() & 15; + final int layer = ly >> 4; + if (!set.hasSection(layer)) { + continue; + } + + int ordinal = set.getBlock(lx, ly, lz).getOrdinal(); + if (ordinal != BlockTypesCache.ReservedIDs.__RESERVED__) { + BlockEntity tile = entry.getValue(); + if (createCopy) { + copy.storeTile(tile); + } + if (PaperLib.isPaper() && tile instanceof BeaconBlockEntity) { + if (beaconPositions == null) { + beaconPositions = new ArrayList<>(); + } + beaconPositions.add(pos.immutable()); + } + nmsChunk.removeBlockEntity(pos); + } + } + } + final BiomeType[][] biomes = set.getBiomes(); + + int bitMask = 0; + synchronized (nmsChunk) { + LevelChunkSection[] levelChunkSections = nmsChunk.getSections(); + + for (int layerNo = getMinSectionPosition(); layerNo <= getMaxSectionPosition(); layerNo++) { + + int getSectionIndex = layerNo - getMinSectionPosition(); + int setSectionIndex = layerNo - set.getMinSectionPosition(); + + if (!set.hasSection(layerNo)) { + // No blocks, but might be biomes present. Handle this lazily. + if (biomes == null) { + continue; + } + if (layerNo < set.getMinSectionPosition() || layerNo > set.getMaxSectionPosition()) { + continue; + } + if (biomes[setSectionIndex] != null) { + synchronized (super.sectionLocks[getSectionIndex]) { + LevelChunkSection existingSection = levelChunkSections[getSectionIndex]; + if (createCopy && existingSection != null) { + copy.storeBiomes(getSectionIndex, existingSection.getBiomes()); + } + + if (existingSection == null) { + PalettedContainer> biomeData = PaperweightPlatformAdapter.getBiomePalettedContainer( + biomes[setSectionIndex], + biomeHolderIdMap + ); + LevelChunkSection newSection = PaperweightPlatformAdapter.newChunkSection( + layerNo, + new char[4096], + adapter, + serverLevel.registryAccess(), + serverLevel.palettedContainerFactory().blockStatesStrategy(), + biomeData + ); + if (PaperweightPlatformAdapter.setSectionAtomic( + nmsWorld.getWorld().getName(), + chunkPos, + levelChunkSections, + null, + newSection, + getSectionIndex + )) { + updateGet(nmsChunk, levelChunkSections, newSection, new char[4096], getSectionIndex); + continue; + } else { + existingSection = levelChunkSections[getSectionIndex]; + if (existingSection == null) { + LOGGER.error("Skipping invalid null section. chunk: {}, {} layer: {}", chunkX, chunkZ, + getSectionIndex + ); + continue; + } + } + } else { + PalettedContainer> paletteBiomes = setBiomesToPalettedContainer( + biomes, + setSectionIndex, + existingSection.getBiomes() + ); + if (paletteBiomes != null) { + PaperweightPlatformAdapter.setBiomesToChunkSection(existingSection, paletteBiomes); + } + } + } + } + continue; + } + + bitMask |= 1 << getSectionIndex; + + // setArr is modified by PaperweightPlatformAdapter#newChunkSection. This is in order to write changes to + // this chunk GET when #updateGet is called. Future dords, please listen this time. + char[] tmp = set.load(layerNo); + char[] setArr = new char[tmp.length]; + System.arraycopy(tmp, 0, setArr, 0, tmp.length); + + // synchronise on internal section to avoid circular locking with a continuing edit if the chunk was + // submitted to keep loaded internal chunks to queue target size. + synchronized (super.sectionLocks[getSectionIndex]) { + + LevelChunkSection newSection; + LevelChunkSection existingSection = levelChunkSections[getSectionIndex]; + // Don't attempt to tick section whilst we're editing + if (existingSection != null) { + PaperweightPlatformAdapter.clearCounts(existingSection); + } + + if (createCopy) { + char[] tmpLoad = load(layerNo); + char[] copyArr = new char[4096]; + System.arraycopy(tmpLoad, 0, copyArr, 0, 4096); + copy.storeSection(getSectionIndex, copyArr); + if (biomes != null && existingSection != null) { + copy.storeBiomes(getSectionIndex, existingSection.getBiomes()); + } + } + + if (existingSection == null) { + + PalettedContainer> biomeData = biomes == null ? + serverLevel.palettedContainerFactory().createForBiomes() : + PaperweightPlatformAdapter.getBiomePalettedContainer(biomes[setSectionIndex], biomeHolderIdMap); + newSection = PaperweightPlatformAdapter.newChunkSection( + layerNo, + setArr, + adapter, + serverLevel.registryAccess(), + serverLevel.palettedContainerFactory().blockStatesStrategy(), + biomeData + ); + if (PaperweightPlatformAdapter.setSectionAtomic( + nmsWorld.getWorld().getName(), + chunkPos, + levelChunkSections, + null, + newSection, + getSectionIndex + )) { + updateGet(nmsChunk, levelChunkSections, newSection, setArr, getSectionIndex); + continue; + } else { + existingSection = levelChunkSections[getSectionIndex]; + if (existingSection == null) { + LOGGER.error("Skipping invalid null section. chunk: {}, {} layer: {}", chunkX, chunkZ, + getSectionIndex + ); + continue; + } + } + } + + //ensure that the server doesn't try to tick the chunksection while we're editing it. (Again) + PaperweightPlatformAdapter.clearCounts(existingSection); + DelegateSemaphore lock = PaperweightPlatformAdapter.applyLock(existingSection); + + // Synchronize to prevent further acquisitions + synchronized (lock) { + lock.acquire(); // Wait until we have the lock + lock.release(); + try { + sectionLock.writeLock().lock(); + if (this.getChunk() != nmsChunk) { + this.levelChunk = nmsChunk; + this.sections = null; + this.reset(); + } else if (existingSection != getSections(false)[getSectionIndex]) { + this.sections[getSectionIndex] = existingSection; + this.reset(); + } else if (!Arrays.equals( + update(getSectionIndex, new char[4096], true), + load(layerNo) + )) { + this.reset(layerNo); + /*} else if (lock.isModified()) { + this.reset(layerNo);*/ + } + } finally { + sectionLock.writeLock().unlock(); + } + + PalettedContainer> biomeData = setBiomesToPalettedContainer( + biomes, + setSectionIndex, + existingSection.getBiomes() + ); + + newSection = PaperweightPlatformAdapter.newChunkSection( + layerNo, + this::load, + setArr, + adapter, + serverLevel.registryAccess(), + serverLevel.palettedContainerFactory().blockStatesStrategy(), + biomeData != null ? biomeData : (PalettedContainer>) existingSection.getBiomes() + ); + if (!PaperweightPlatformAdapter.setSectionAtomic( + nmsWorld.getWorld().getName(), + chunkPos, + levelChunkSections, + existingSection, + newSection, + getSectionIndex + )) { + LOGGER.error("Skipping invalid null section. chunk: {}, {} layer: {}", chunkX, chunkZ, + getSectionIndex + ); + } else { + updateGet(nmsChunk, levelChunkSections, newSection, setArr, getSectionIndex); + } + } + } + } + + Map heightMaps = set.getHeightMaps(); + for (Map.Entry entry : heightMaps.entrySet()) { + PaperweightGetBlocks.this.setHeightmapToGet(entry.getKey(), entry.getValue()); + } + PaperweightGetBlocks.this.setLightingToGet( + set.getLight(), + set.getMinSectionPosition(), + set.getMaxSectionPosition() + ); + PaperweightGetBlocks.this.setSkyLightingToGet( + set.getSkyLight(), + set.getMinSectionPosition(), + set.getMaxSectionPosition() + ); + + List syncTasks = new ArrayList<>(); + + int bx = chunkX << 4; + int bz = chunkZ << 4; + + // Call beacon deactivate events here synchronously + // list will be null on spigot, so this is an implicit isPaper check + if (beaconPositions != null && !beaconPositions.isEmpty()) { + final List finalBeaconPositions = beaconPositions; + + if (FoliaUtil.isFoliaServer()) { + for (BlockPos beaconPos : finalBeaconPositions) { + Location location = new Location( + nmsWorld.getWorld(), + beaconPos.getX(), + beaconPos.getY(), + beaconPos.getZ() + ); + Bukkit.getServer().getRegionScheduler().execute( + WorldEditPlugin.getInstance(), + location, + () -> { + BeaconBlockEntity.playSound(nmsWorld, beaconPos, SoundEvents.BEACON_DEACTIVATE); + new BeaconDeactivatedEvent(CraftBlock.at(nmsWorld, beaconPos)).callEvent(); + } + ); + } + } else { + syncTasks.add(() -> { + for (BlockPos beaconPos : finalBeaconPositions) { + BeaconBlockEntity.playSound(nmsWorld, beaconPos, SoundEvents.BEACON_DEACTIVATE); + new BeaconDeactivatedEvent(CraftBlock.at(nmsWorld, beaconPos)).callEvent(); + } + }); + } + } + + Set entityRemoves = set.getEntityRemoves(); + if (entityRemoves != null && !entityRemoves.isEmpty()) { + + syncTasks.add(() -> { + Set entitiesRemoved = new HashSet<>(); + final List entities = PaperweightPlatformAdapter.getEntities(nmsChunk); + + for (Entity entity : entities) { + UUID uuid = entity.getUUID(); + if (entityRemoves.contains(uuid)) { + if (createCopy) { + copy.storeEntity(entity); + } + removeEntity(entity); + entitiesRemoved.add(uuid); + entityRemoves.remove(uuid); + } + } + if (Settings.settings().EXPERIMENTAL.REMOVE_ENTITY_FROM_WORLD_ON_CHUNK_FAIL) { + for (UUID uuid : entityRemoves) { + Entity entity = nmsWorld.getEntities().get(uuid); + if (entity != null) { + removeEntity(entity); + } + } + } + // Only save entities that were actually removed to history + set.getEntityRemoves().clear(); + set.getEntityRemoves().addAll(entitiesRemoved); + }); + } + + Collection entities = set.entities(); + if (entities != null && !entities.isEmpty()) { + + syncTasks.add(() -> { + Iterator iterator = entities.iterator(); + while (iterator.hasNext()) { + final FaweCompoundTag nativeTag = iterator.next(); + final LinCompoundTag linTag = nativeTag.linTag(); + final LinStringTag idTag = linTag.findTag("Id", LinTagType.stringTag()); + final LinListTag posTag = linTag.findListTag("Pos", LinTagType.doubleTag()); + final LinListTag rotTag = linTag.findListTag("Rotation", LinTagType.floatTag()); + if (idTag == null || posTag == null || rotTag == null) { + LOGGER.error("Unknown entity tag: {}", nativeTag); + continue; + } + final double x = posTag.get(0).valueAsDouble(); + final double y = posTag.get(1).valueAsDouble(); + final double z = posTag.get(2).valueAsDouble(); + final float yaw = rotTag.get(0).valueAsFloat(); + final float pitch = rotTag.get(1).valueAsFloat(); + final String id = idTag.value(); + + EntityType type = EntityType.byString(id).orElse(null); + if (type != null) { + Entity entity = type.create(nmsWorld, EntitySpawnReason.COMMAND); + if (entity != null) { + final LinCompoundTag.Builder toLoadComponentBuilder = linTag.toBuilder(); + for (final String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { + toLoadComponentBuilder.remove(name); + } + ValueInput input = createInput(toLoadComponentBuilder.build()); + entity.load(input); + entity.absSnapTo(x, y, z, yaw, pitch); + entity.setUUID(NbtUtils.uuid(nativeTag)); + Runnable onError = () -> LOGGER.warn( + "Error creating entity of type `{}` in world `{}` at location `{},{},{}`", + id, + nmsWorld.getWorld().getName(), + x, + y, + z + ); + if (FoliaUtil.isFoliaServer()) { + Location location = new Location(nmsWorld.getWorld(), x, y, z); + Bukkit.getServer().getRegionScheduler().execute(WorldEditPlugin.getInstance(), location, () -> { + if (!nmsWorld.addFreshEntity(entity, CreatureSpawnEvent.SpawnReason.CUSTOM)) { + onError.run(); + iterator.remove(); + } + }); + } else { + if (!set.getSideEffectSet().shouldApply(SideEffect.ENTITY_EVENTS)) { + entity.spawnReason = CreatureSpawnEvent.SpawnReason.CUSTOM; + entity.generation = false; + if (PaperLib.isPaper()) { + if (!nmsWorld.moonrise$getEntityLookup().addNewEntity(entity, false)) { + onError.run(); + } + continue; + } + // Not paper + try { + PaperweightPlatformAdapter.getEntitySectionManager(nmsWorld).addNewEntity(entity); + continue; + } catch (IllegalAccessException e) { + // Fallback + LOGGER.warn("Error bypassing entity events on spawn on Spigot", e); + } + } + if (!nmsWorld.addFreshEntity(entity, CreatureSpawnEvent.SpawnReason.CUSTOM)) { + onError.run(); + // Unsuccessful create should not be saved to history + iterator.remove(); + } + } + } + } + } + }); + } + + // set tiles + Map tiles = set.tiles(); + if (tiles != null && !tiles.isEmpty()) { + + syncTasks.add(() -> { + for (final Map.Entry entry : tiles.entrySet()) { + final FaweCompoundTag nativeTag = entry.getValue(); + final BlockVector3 blockHash = entry.getKey(); + final int x = blockHash.x() + bx; + final int y = blockHash.y(); + final int z = blockHash.z() + bz; + final BlockPos pos = new BlockPos(x, y, z); + + if (FoliaUtil.isFoliaServer()) { + Location location = new Location(nmsWorld.getWorld(), x, y, z); + Bukkit.getServer().getRegionScheduler().execute(WorldEditPlugin.getInstance(), location, () -> { + synchronized (nmsWorld) { + BlockEntity tileEntity = nmsWorld.getBlockEntity(pos); + if (tileEntity == null || tileEntity.isRemoved()) { + nmsWorld.removeBlockEntity(pos); + tileEntity = nmsWorld.getBlockEntity(pos); + } + if (tileEntity != null) { + ValueInput input = createInput(nativeTag.linTag().toBuilder() + .putInt("x", x).putInt("y", y).putInt("z", z) + .build() + ); + tileEntity.loadWithComponents(input); + } + } + }); + } else { + synchronized (nmsWorld) { + BlockEntity tileEntity = nmsWorld.getBlockEntity(pos); + if (tileEntity == null || tileEntity.isRemoved()) { + nmsWorld.removeBlockEntity(pos); + tileEntity = nmsWorld.getBlockEntity(pos); + } + if (tileEntity != null) { + ValueInput input = createInput(nativeTag.linTag().toBuilder() + .putInt("x", x).putInt("y", y).putInt("z", z) + .build() + ); + tileEntity.loadWithComponents(input); + } + } + } + } + }); + } + + Runnable callback; + if (bitMask == 0 && biomes == null && !lightUpdate) { + callback = null; + } else { + int finalMask = bitMask != 0 ? bitMask : lightUpdate ? set.getBitMask() : 0; + syncTasks.add(() -> { + // Set Modified + nmsChunk.setLightCorrect(true); + nmsChunk.mustNotSave = false; + }); + callback = () -> { + // send to player + if (!set + .getSideEffectSet() + .shouldApply(SideEffect.LIGHTING) || !Settings.settings().LIGHTING.DELAY_PACKET_SENDING || finalMask == 0 && biomes != null) { + this.send(); + } + if (finalizer != null) { + finalizer.run(); + } + }; + } + return handleCallFinalizer(syncTasks, callback, finalizer); + } + } + + private void updateGet( + LevelChunk nmsChunk, + LevelChunkSection[] chunkSections, + LevelChunkSection section, + char[] arr, + int layer + ) { + try { + sectionLock.writeLock().lock(); + if (this.getChunk() != nmsChunk) { + this.levelChunk = nmsChunk; + this.sections = new LevelChunkSection[chunkSections.length]; + System.arraycopy(chunkSections, 0, this.sections, 0, chunkSections.length); + this.reset(); + } + if (this.sections == null) { + this.sections = new LevelChunkSection[chunkSections.length]; + System.arraycopy(chunkSections, 0, this.sections, 0, chunkSections.length); + } + if (this.sections[layer] != section) { + // Not sure why it's funky, but it's what I did in commit fda7d00747abe97d7891b80ed8bb88d97e1c70d1 and I don't want to touch it >dords + this.sections[layer] = new LevelChunkSection[]{section}.clone()[0]; + } + } finally { + sectionLock.writeLock().unlock(); + } + this.blocks[layer] = arr; + } + + @Override + public void send() { + synchronized (sendLock) { + PaperweightPlatformAdapter.sendChunk(new IntPair(chunkX, chunkZ), serverLevel, chunkX, chunkZ); + } + } + + /** + * Update a given (nullable) data array to the current data stored in the server's chunk, associated with this + * {@link PaperweightPlatformAdapter} instance. Not synchronised to the {@link PaperweightPlatformAdapter} instance as synchronisation + * is handled where necessary in the method, and should otherwise be handled correctly by this method's caller. + * + * @param layer layer index (0 may denote a negative layer in the world, e.g. at y=-32) + * @param data array to be updated/filled with data or null + * @param aggressive if the cached section array should be re-acquired. + * @return the given array to be filled with data, or a new array if null is given. + */ + @Override + @SuppressWarnings("unchecked") + public char[] update(int layer, char[] data, boolean aggressive) { + LevelChunkSection section = getSections(aggressive)[layer]; + // Section is null, return empty array + if (section == null) { + data = new char[4096]; + Arrays.fill(data, (char) BlockTypesCache.ReservedIDs.AIR); + return data; + } + if (data == null || data == FaweCache.INSTANCE.EMPTY_CHAR_4096 || data.length != 4096) { + data = new char[4096]; // new array, will be populated below + } + Semaphore lock = PaperweightPlatformAdapter.applyLock(section); + synchronized (lock) { + // Efficiently convert ChunkSection to raw data + try { + lock.acquire(); + + final PalettedContainer blocks = section.getStates(); + final Object dataObject = PaperweightPlatformAdapter.fieldData.get(blocks); + final BitStorage bits = (BitStorage) PaperweightPlatformAdapter.fieldStorage.get(dataObject); + + if (bits instanceof ZeroBitStorage) { + Arrays.fill(data, adapter.adaptToChar(blocks.get(0, 0, 0))); // get(int) is only public on paper + return data; + } + + final Palette palette = (Palette) PaperweightPlatformAdapter.fieldPalette.get(dataObject); + + final int bitsPerEntry = bits.getBits(); + final long[] blockStates = bits.getRaw(); + + new BitArrayUnstretched(bitsPerEntry, 4096, blockStates).toRaw(data); + + int num_palette; + if (palette instanceof LinearPalette || palette instanceof HashMapPalette) { + num_palette = palette.getSize(); + } else { + // The section's palette is the global block palette. + adapter.mapFromGlobalPalette(data); + return data; + } + + char[] paletteToOrdinal = FaweCache.INSTANCE.PALETTE_TO_BLOCK_CHAR.get(); + try { + if (num_palette == 1) { + char ordinal = ordinal(palette.valueFor(0), adapter); + Arrays.fill(data, ordinal); + } else { + for (int i = 0; i < num_palette; i++) { + char ordinal = ordinal(palette.valueFor(i), adapter); + paletteToOrdinal[i] = ordinal; + } + adapter.mapWithPalette(data, paletteToOrdinal); + } + } finally { + Arrays.fill(paletteToOrdinal, 0, num_palette, Character.MAX_VALUE); + } + return data; + } catch (IllegalAccessException | InterruptedException e) { + LOGGER.error("Could not read block data from palette", e); + throw new RuntimeException(e); + } finally { + lock.release(); + } + } + } + + private char ordinal(BlockState ibd, PaperweightFaweAdapter adapter) { + if (ibd == null) { + return BlockTypesCache.ReservedIDs.AIR; + } else { + return adapter.adaptToChar(ibd); + } + } + + public LevelChunkSection[] getSections(boolean force) { + force &= forceLoadSections; + LevelChunkSection[] tmp = sections; + if (tmp == null || force) { + try { + sectionLock.writeLock().lock(); + tmp = sections; + if (tmp == null || force) { + LevelChunkSection[] chunkSections = getChunk().getSections(); + tmp = new LevelChunkSection[chunkSections.length]; + System.arraycopy(chunkSections, 0, tmp, 0, chunkSections.length); + sections = tmp; + } + } finally { + sectionLock.writeLock().unlock(); + } + } + return tmp; + } + + public LevelChunk getChunk() { + LevelChunk levelChunk = this.levelChunk; + if (levelChunk == null) { + synchronized (this) { + levelChunk = this.levelChunk; + if (levelChunk == null) { + try { + this.levelChunk = levelChunk = ensureLoaded(this.serverLevel).get(); + } catch (InterruptedException | ExecutionException e) { + LOGGER.error("Could not get chunk at {},{}", chunkX, chunkZ, e); + throw new FaweException( + TextComponent.of("Could not get chunk at " + chunkX + "," + chunkZ + ": " + e.getMessage()), + FaweException.Type.OTHER, + false + ); + } + } + } + } + return levelChunk; + } + + private void fillLightNibble(char[][] light, LightLayer lightLayer, int minSectionPosition, int maxSectionPosition) { + for (int Y = 0; Y <= maxSectionPosition - minSectionPosition; Y++) { + if (light[Y] == null) { + continue; + } + SectionPos sectionPos = SectionPos.of(levelChunk.getPos(), Y + minSectionPosition); + DataLayer dataLayer = serverLevel.getChunkSource().getLightEngine().getLayerListener(lightLayer).getDataLayerData( + sectionPos); + if (dataLayer == null) { + byte[] LAYER_COUNT = new byte[2048]; + Arrays.fill(LAYER_COUNT, lightLayer == LightLayer.SKY ? (byte) 15 : (byte) 0); + dataLayer = new DataLayer(LAYER_COUNT); + ((LevelLightEngine) serverLevel.getChunkSource().getLightEngine()).queueSectionData( + lightLayer, + sectionPos, + dataLayer + ); + } + synchronized (dataLayer) { + for (int x = 0; x < 16; x++) { + for (int y = 0; y < 16; y++) { + for (int z = 0; z < 16; z++) { + int i = y << 8 | z << 4 | x; + if (light[Y][i] < 16) { + dataLayer.set(x, y, z, light[Y][i]); + } + } + } + } + } + } + } + + private PalettedContainer> setBiomesToPalettedContainer( + final BiomeType[][] biomes, + final int sectionIndex, + final PalettedContainerRO> data + ) { + BiomeType[] sectionBiomes; + if (biomes == null || (sectionBiomes = biomes[sectionIndex]) == null) { + return null; + } + PalettedContainer> biomeData = data.recreate(); + for (int y = 0, index = 0; y < 4; y++) { + for (int z = 0; z < 4; z++) { + for (int x = 0; x < 4; x++, index++) { + BiomeType biomeType = sectionBiomes[index]; + if (biomeType == null) { + biomeData.set(x, y, z, data.get(x, y, z)); + } else { + biomeData.set( + x, + y, + z, + biomeHolderIdMap.byIdOrThrow(adapter.getInternalBiomeId(biomeType)) + ); + } + } + } + } + return biomeData; + } + + @Override + public boolean hasSection(int layer) { + layer -= getMinSectionPosition(); + return getSections(false)[layer] != null; + } + + @Override + public boolean hasNonEmptySection(int layer) { + layer -= getMinSectionPosition(); + LevelChunkSection section = getSections(false)[layer]; + return section != null && !section.hasOnlyAir(); + } + + @Override + @SuppressWarnings("unchecked") + public boolean trim(boolean aggressive) { + synchronized (this) { + if (sections == null && (!aggressive || levelChunk == null)) { + skyLight = new DataLayer[getSectionCount()]; + blockLight = new DataLayer[getSectionCount()]; + return !aggressive || super.trim(true); + } + } + if (aggressive) { + sectionLock.writeLock().lock(); + try { + synchronized (this) { + skyLight = new DataLayer[getSectionCount()]; + blockLight = new DataLayer[getSectionCount()]; + sections = null; + levelChunk = null; + return super.trim(true); + } + } finally { + sectionLock.writeLock().unlock(); + } + } + synchronized (this) { + for (int i = getMinSectionPosition(); i <= getMaxSectionPosition(); i++) { + int layer = i - getMinSectionPosition(); + if (!hasSection(i) || super.blocks[layer] == null) { + continue; + } + LevelChunkSection existing = getSections(true)[layer]; + try { + final PalettedContainer blocksExisting = existing.getStates(); + + final Object dataObject = PaperweightPlatformAdapter.fieldData.get(blocksExisting); + final Palette palette = (Palette) PaperweightPlatformAdapter.fieldPalette.get( + dataObject); + int paletteSize; + + if (palette instanceof LinearPalette || palette instanceof HashMapPalette) { + paletteSize = palette.getSize(); + } else { + super.trim(false, i); + continue; + } + if (paletteSize == 1) { + //If the cached palette size is 1 then no blocks can have been changed i.e. do not need to update these chunks. + continue; + } + super.trim(false, i); + } catch (IllegalAccessException ignored) { + super.trim(false, i); + } + } + return true; + } + } + +} diff --git a/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightGetBlocks_Copy.java b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightGetBlocks_Copy.java new file mode 100644 index 0000000000..5e661823c9 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightGetBlocks_Copy.java @@ -0,0 +1,295 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1; + +import com.fastasyncworldedit.core.extent.processor.heightmap.HeightMapType; +import com.fastasyncworldedit.core.nbt.FaweCompoundTag; +import com.fastasyncworldedit.core.queue.IBlocks; +import com.fastasyncworldedit.core.queue.IChunk; +import com.fastasyncworldedit.core.queue.IChunkGet; +import com.fastasyncworldedit.core.queue.IChunkSet; +import com.fastasyncworldedit.core.queue.IQueueExtent; +import com.fastasyncworldedit.core.util.NbtUtils; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; +import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1.PaperweightPlatformAdapter; +import com.sk89q.worldedit.internal.util.LogManagerCompat; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.biome.BiomeType; +import com.sk89q.worldedit.world.block.BaseBlock; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockTypesCache; +import io.papermc.lib.PaperLib; +import net.minecraft.core.Holder; +import net.minecraft.nbt.Tag; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.PalettedContainer; +import net.minecraft.world.level.chunk.PalettedContainerRO; +import org.apache.logging.log4j.Logger; + +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.Future; + +import static com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1.PaperweightPlatformAdapter.createOutput; + +public class PaperweightGetBlocks_Copy implements IChunkGet { + + private static final Logger LOGGER = LogManagerCompat.getLogger(); + + private final Map tiles = new HashMap<>(); + private final Set entities = new HashSet<>(); + private final char[][] blocks; + private final int minHeight; + private final int maxHeight; + private final int chunkX; + private final int chunkZ; + final ServerLevel serverLevel; + final LevelChunk levelChunk; + private Holder[][] biomes = null; + + protected PaperweightGetBlocks_Copy(LevelChunk levelChunk) { + this.levelChunk = levelChunk; + this.serverLevel = levelChunk.level; + this.minHeight = serverLevel.getMinY(); + this.maxHeight = serverLevel.getMaxY() - 1; // Minecraft max limit is exclusive. + this.blocks = new char[getSectionCount()][]; + this.chunkX = levelChunk.getPos().x(); + this.chunkZ = levelChunk.getPos().z(); + } + + protected void storeTile(BlockEntity blockEntity) { + LinValueOutput output = createOutput(); + blockEntity.saveWithId(output); + tiles.put( + BlockVector3.at( + blockEntity.getBlockPos().getX(), + blockEntity.getBlockPos().getY(), + blockEntity.getBlockPos().getZ() + ), + FaweCompoundTag.of(output::buildResult) + ); + } + + protected void storeEntity(Entity entity) { + LinValueOutput output = createOutput(); + entity.save(output); + entities.add(FaweCompoundTag.of(output::buildResult)); + } + + @Override + public Collection entities() { + return this.entities; + } + + @Override + public Set getFullEntities() { + throw new UnsupportedOperationException("Cannot get full entities from GET copy."); + } + + @Override + public @Nullable FaweCompoundTag entity(final UUID uuid) { + for (FaweCompoundTag tag : entities) { + if (uuid.equals(NbtUtils.uuid(tag))) { + return tag; + } + } + return null; + } + + @Override + public boolean isCreateCopy() { + return false; + } + + @Override + public int setCreateCopy(boolean createCopy) { + return -1; + } + + @Override + public void setLightingToGet(char[][] lighting, int minSectionPosition, int maxSectionPosition) { + } + + @Override + public void setSkyLightingToGet(char[][] lighting, int minSectionPosition, int maxSectionPosition) { + } + + @Override + public void setHeightmapToGet(HeightMapType type, int[] data) { + } + + @Override + public int getMaxY() { + return maxHeight; + } + + @Override + public int getMinY() { + return minHeight; + } + + @Override + public int getMaxSectionPosition() { + return maxHeight >> 4; + } + + @Override + public int getMinSectionPosition() { + return minHeight >> 4; + } + + @Override + public int getX() { + return chunkX; + } + + @Override + public int getZ() { + return chunkZ; + } + + @Override + public BiomeType getBiomeType(int x, int y, int z) { + Holder biome = biomes[(y >> 4) - getMinSectionPosition()][(y & 12) << 2 | (z & 12) | (x & 12) >> 2]; + return PaperweightPlatformAdapter.adapt(biome, serverLevel); + } + + @Override + public void removeSectionLighting(int layer, boolean sky) { + } + + @Override + public boolean trim(boolean aggressive, int layer) { + return false; + } + + @Override + public IBlocks reset() { + return null; + } + + @Override + public int getSectionCount() { + return serverLevel.getSectionsCount(); + } + + protected void storeSection(int layer, char[] data) { + blocks[layer] = data; + } + + protected void storeBiomes(int layer, PalettedContainerRO> biomeData) { + if (biomes == null) { + biomes = new Holder[getSectionCount()][]; + } + if (biomes[layer] == null) { + biomes[layer] = new Holder[64]; + } + if (biomeData instanceof PalettedContainer> palettedContainer) { + if (PaperLib.isPaper()) { + for (int i = 0; i < 64; i++) { + biomes[layer][i] = palettedContainer.get(i); // Only public on paper + } + } else { + try { + for (int i = 0; i < 64; i++) { + biomes[layer][i] = + (Holder) PaperweightPlatformAdapter.PALETTED_CONTAINER_GET.invoke(palettedContainer, i); + } + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + } else { + LOGGER.error( + "Cannot correctly save biomes to history. Expected class type {} but got {}", + PalettedContainer.class.getSimpleName(), + biomeData.getClass().getSimpleName() + ); + } + } + + @Override + public BaseBlock getFullBlock(int x, int y, int z) { + BlockState state = BlockTypesCache.states[get(x, y, z)]; + return state.toBaseBlock((IBlocks) this, x, y, z); + } + + @Override + public boolean hasSection(int layer) { + layer -= getMinSectionPosition(); + return blocks[layer] != null; + } + + @Override + public char[] load(int layer) { + layer -= getMinSectionPosition(); + if (blocks[layer] == null) { + blocks[layer] = new char[4096]; + Arrays.fill(blocks[layer], (char) BlockTypesCache.ReservedIDs.AIR); + } + return blocks[layer]; + } + + @Override + public char[] loadIfPresent(int layer) { + layer -= getMinSectionPosition(); + return blocks[layer]; + } + + @Override + public BlockState getBlock(int x, int y, int z) { + return BlockTypesCache.states[get(x, y, z)]; + } + + @Override + public Map tiles() { + return tiles; + } + + @Override + public @Nullable FaweCompoundTag tile(final int x, final int y, final int z) { + return tiles.get(BlockVector3.at(x, y, z)); + } + + @Override + public int getSkyLight(int x, int y, int z) { + return 0; + } + + @Override + public int getEmittedLight(int x, int y, int z) { + return 0; + } + + @Override + public int[] getHeightMap(HeightMapType type) { + return new int[0]; + } + + @Override + public > T call(IQueueExtent owner, IChunkSet set, Runnable finalize) { + return null; + } + + public char get(int x, int y, int z) { + final int layer = (y >> 4) - getMinSectionPosition(); + final int index = (y & 15) << 8 | z << 4 | x; + return blocks[layer][index]; + } + + + @Override + public boolean trim(boolean aggressive) { + return false; + } + +} diff --git a/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightLevelProxy.java b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightLevelProxy.java new file mode 100644 index 0000000000..4a8a4a235d --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightLevelProxy.java @@ -0,0 +1,141 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1; + +import com.fastasyncworldedit.core.util.ReflectionUtils; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1.PaperweightFaweAdapter; +import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1.PaperweightPlacementStateProcessor; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.tags.FluidTags; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.border.WorldBorder; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.material.Fluids; +import net.minecraft.world.level.storage.ValueInput; +import org.enginehub.linbus.tree.LinCompoundTag; +import sun.misc.Unsafe; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import static com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1.PaperweightPlatformAdapter.createInput; + +public class PaperweightLevelProxy extends ServerLevel { + + protected ServerLevel serverLevel; + private PaperweightPlacementStateProcessor processor; + private PaperweightFaweAdapter adapter; + + @SuppressWarnings("DataFlowIssue") + private PaperweightLevelProxy() { + super(null, null, null, null, null, null, true, 0L, null, true, null, null, null, null, null, null); + throw new IllegalStateException("Cannot be instantiated"); + } + + public static PaperweightLevelProxy getInstance(ServerLevel serverLevel, PaperweightPlacementStateProcessor processor) { + Unsafe unsafe = ReflectionUtils.getUnsafe(); + + PaperweightLevelProxy newLevel; + try { + newLevel = (PaperweightLevelProxy) unsafe.allocateInstance(PaperweightLevelProxy.class); + } catch (InstantiationException e) { + throw new RuntimeException(e); + } + newLevel.processor = processor; + newLevel.adapter = ((PaperweightFaweAdapter) WorldEditPlugin.getInstance().getBukkitImplAdapter()); + newLevel.serverLevel = serverLevel; + return newLevel; + } + + @Nullable + @Override + public BlockEntity getBlockEntity(@Nonnull BlockPos blockPos) { + if (blockPos.getX() == Integer.MAX_VALUE) { + return null; + } + LinCompoundTag tag = processor.getTileAt(blockPos.getX(), blockPos.getY(), blockPos.getZ()); + if (tag == null) { + return null; + } + BlockState state = adapter.adapt(processor.getBlockStateAt(blockPos.getX(), blockPos.getY(), blockPos.getZ())); + if (!(state.getBlock() instanceof EntityBlock entityBlock)) { + return null; + } + BlockEntity tileEntity = entityBlock.newBlockEntity(blockPos, state); + ValueInput input = createInput(tag); + tileEntity.loadWithComponents(input); + return tileEntity; + } + + @Override + @Nonnull + public BlockState getBlockState(@Nonnull BlockPos blockPos) { + if (blockPos.getX() == Integer.MAX_VALUE) { + return Blocks.AIR.defaultBlockState(); + } + com.sk89q.worldedit.world.block.BlockState state = processor.getBlockStateAt( + blockPos.getX(), + blockPos.getY(), + blockPos.getZ() + ); + return adapter.adapt(state); + } + + @SuppressWarnings("unused") + @Override + @Nonnull + public FluidState getFluidState(@Nonnull BlockPos pos) { + if (pos.getX() == Integer.MAX_VALUE) { + return Fluids.EMPTY.defaultFluidState(); + } + return getBlockState(pos).getFluidState(); + } + + @SuppressWarnings("unused") + @Override + public boolean isWaterAt(@Nonnull BlockPos pos) { + if (pos.getX() == Integer.MAX_VALUE) { + return false; + } + return getBlockState(pos).getFluidState().is(FluidTags.WATER); + } + + @Override + public int getHeight() { + return serverLevel.getHeight(); + } + + @Override + public int getMinY() { + return serverLevel.getMinY(); + } + + @Override + public int getMaxY() { + return serverLevel.getMaxY(); + } + + @Override + public boolean isInsideBuildHeight(int blockY) { + return serverLevel.isInsideBuildHeight(blockY); + } + + @Override + public boolean isOutsideBuildHeight(BlockPos pos) { + return serverLevel.isOutsideBuildHeight(pos); + } + + @Override + public boolean isOutsideBuildHeight(int blockY) { + return serverLevel.isOutsideBuildHeight(blockY); + } + + @Override + public WorldBorder getWorldBorder() { + return serverLevel.getWorldBorder(); + } + +} diff --git a/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightMapChunkUtil.java b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightMapChunkUtil.java new file mode 100644 index 0000000000..3454a2e079 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightMapChunkUtil.java @@ -0,0 +1,34 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1; + +import com.fastasyncworldedit.bukkit.adapter.MapChunkUtil; +import com.sk89q.worldedit.bukkit.adapter.Refraction; +import net.minecraft.network.protocol.game.ClientboundLevelChunkPacketData; +import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; + +//TODO un-very-break-this +public class PaperweightMapChunkUtil extends MapChunkUtil { + + public PaperweightMapChunkUtil() throws NoSuchFieldException { + fieldX = ClientboundLevelChunkPacketData.class.getDeclaredField(Refraction.pickName("TWO_MEGABYTES", "a")); + fieldZ = ClientboundLevelChunkWithLightPacket.class.getDeclaredField(Refraction.pickName("x", "b")); + fieldBitMask = ClientboundLevelChunkWithLightPacket.class.getDeclaredField(Refraction.pickName("z", "c")); + fieldHeightMap = ClientboundLevelChunkPacketData.class.getDeclaredField(Refraction.pickName("heightmaps", "b")); + fieldChunkData = ClientboundLevelChunkWithLightPacket.class.getDeclaredField(Refraction.pickName("chunkData", "d")); + fieldBlockEntities = ClientboundLevelChunkPacketData.class.getDeclaredField(Refraction.pickName("buffer", "c")); + fieldFull = ClientboundLevelChunkPacketData.class.getDeclaredField(Refraction.pickName("blockEntitiesData", "d")); + fieldX.setAccessible(true); + fieldZ.setAccessible(true); + fieldBitMask.setAccessible(true); + fieldHeightMap.setAccessible(true); + fieldChunkData.setAccessible(true); + fieldBlockEntities.setAccessible(true); + fieldFull.setAccessible(true); + } + + @Override + public ClientboundLevelChunkWithLightPacket createPacket() { + // TODO ??? return new ClientboundLevelChunkPacket(); + throw new UnsupportedOperationException(); + } + +} diff --git a/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightPlacementStateProcessor.java b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightPlacementStateProcessor.java new file mode 100644 index 0000000000..ab116db196 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightPlacementStateProcessor.java @@ -0,0 +1,113 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1; + +import com.fastasyncworldedit.core.extent.processor.PlacementStateProcessor; +import com.fastasyncworldedit.core.util.ExtentTraverser; +import com.fastasyncworldedit.core.wrappers.WorldWrapper; +import com.sk89q.worldedit.bukkit.BukkitWorld; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1.PaperweightFaweMutableBlockPlaceContext; +import com.sk89q.worldedit.bukkit.adapter.impl.v26_1.PaperweightBlockMaterial; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.function.mask.BlockTypeMask; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.math.Vector3; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.util.Direction; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockTypesCache; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.Vec3; +import org.bukkit.craftbukkit.CraftWorld; + +import javax.annotation.Nullable; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +public class PaperweightPlacementStateProcessor extends PlacementStateProcessor { + + private final PaperweightFaweAdapter adapter = ((PaperweightFaweAdapter) WorldEditPlugin + .getInstance() + .getBukkitImplAdapter()); + private final PaperweightFaweMutableBlockPlaceContext mutableBlockPlaceContext; + private final PaperweightLevelProxy proxyLevel; + + public PaperweightPlacementStateProcessor(Extent extent, BlockTypeMask mask, Region region) { + super(extent, mask, region); + World world = ExtentTraverser.getWorldFromExtent(extent); + if (world == null) { + throw new UnsupportedOperationException( + "World is required for PlacementStateProcessor but none found in given extent."); + } + BukkitWorld bukkitWorld; + if (world instanceof WorldWrapper wrapper) { + bukkitWorld = (BukkitWorld) wrapper.getParent(); + } else { + bukkitWorld = (BukkitWorld) world; + } + this.proxyLevel = PaperweightLevelProxy.getInstance(((CraftWorld) bukkitWorld.getWorld()).getHandle(), this); + this.mutableBlockPlaceContext = new PaperweightFaweMutableBlockPlaceContext(proxyLevel); + } + + private PaperweightPlacementStateProcessor( + Extent extent, + BlockTypeMask mask, + Map crossChunkSecondPasses, + ServerLevel serverLevel, + ThreadLocal threadProcessors, + Region region, + AtomicBoolean finished + ) { + super(extent, mask, crossChunkSecondPasses, threadProcessors, region, finished); + this.proxyLevel = PaperweightLevelProxy.getInstance(serverLevel, this); + this.mutableBlockPlaceContext = new PaperweightFaweMutableBlockPlaceContext(proxyLevel); + } + + @Override + protected char getStateAtFor( + int x, + int y, + int z, + BlockState state, + Vector3 clickPos, + Direction clickedFaceDirection, + BlockVector3 clickedBlock + ) { + Block block = ((PaperweightBlockMaterial) state.getMaterial()).getBlock(); + Vec3 pos = new Vec3(clickPos.x(), clickPos.y(), clickPos.z()); + net.minecraft.core.Direction side = net.minecraft.core.Direction.valueOf(clickedFaceDirection.toString()); + BlockPos blockPos = new BlockPos(clickedBlock.x(), clickedBlock.y(), clickedBlock.z()); + net.minecraft.world.level.block.state.BlockState newState = block.getStateForPlacement(mutableBlockPlaceContext.withSetting( + new BlockHitResult(pos, side, blockPos, false), + side.getOpposite() + )); + return newState == null ? BlockTypesCache.ReservedIDs.AIR : adapter.ibdIDToOrdinal(Block.BLOCK_STATE_REGISTRY.getId( + newState)); + } + + @Override + @Nullable + public Extent construct(Extent child) { + if (child == getExtent()) { + return this; + } + return new PaperweightPlacementStateProcessor(child, mask, region); + } + + @Override + public PlacementStateProcessor fork() { + return new PaperweightPlacementStateProcessor( + extent, + mask, + postCompleteSecondPasses, + proxyLevel.serverLevel, + threadProcessors, + region, + finished + ); + } + +} diff --git a/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightPlatformAdapter.java b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightPlatformAdapter.java new file mode 100644 index 0000000000..1f4c048939 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightPlatformAdapter.java @@ -0,0 +1,690 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1; + +import ca.spottedleaf.moonrise.patches.chunk_system.level.entity.ChunkEntitySlices; +import ca.spottedleaf.moonrise.patches.chunk_system.scheduling.ChunkHolderManager; +import com.fastasyncworldedit.bukkit.adapter.CachedBukkitAdapter; +import com.fastasyncworldedit.bukkit.adapter.DelegateSemaphore; +import com.fastasyncworldedit.bukkit.adapter.NMSAdapter; +import com.fastasyncworldedit.core.Fawe; +import com.fastasyncworldedit.core.FaweCache; +import com.fastasyncworldedit.core.math.BitArrayUnstretched; +import com.fastasyncworldedit.core.math.IntPair; +import com.fastasyncworldedit.core.util.FoliaUtil; +import com.fastasyncworldedit.core.util.MathMan; +import com.fastasyncworldedit.core.util.TaskManager; +import com.mojang.serialization.DataResult; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; +import com.sk89q.worldedit.bukkit.adapter.Refraction; +import com.sk89q.worldedit.bukkit.adapter.impl.v26_1.PaperweightBlockMaterial; +import com.sk89q.worldedit.internal.util.LogManagerCompat; +import com.sk89q.worldedit.world.biome.BiomeType; +import com.sk89q.worldedit.world.biome.BiomeTypes; +import com.sk89q.worldedit.world.block.BlockTypesCache; +import io.papermc.lib.PaperLib; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.IdMap; +import net.minecraft.core.Registry; +import net.minecraft.core.RegistryAccess; +import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.dedicated.DedicatedServer; +import net.minecraft.server.level.ChunkHolder; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.Mth; +import net.minecraft.util.ProblemReporter; +import net.minecraft.util.ThreadingDetector; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.chunk.PalettedContainer; +import net.minecraft.world.level.chunk.PalettedContainerFactory; +import net.minecraft.world.level.chunk.PalettedContainerRO; +import net.minecraft.world.level.chunk.Strategy; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.entity.PersistentEntitySectionManager; +import org.apache.logging.log4j.Logger; +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.craftbukkit.CraftChunk; +import org.enginehub.linbus.tree.LinCompoundTag; + +import javax.annotation.Nullable; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Semaphore; +import java.util.function.IntFunction; +import java.util.stream.LongStream; + +import static net.minecraft.core.registries.Registries.BIOME; + +public final class PaperweightPlatformAdapter extends NMSAdapter { + + public static final Field fieldData; + + public static final Constructor dataConstructor; + + public static final Field fieldStorage; + public static final Field fieldPalette; + + private static final MethodHandle palettedContainerUnpackSpigot; + + private static final Field fieldTickingFluidCount; + private static final Field fieldTickingBlockCount; + private static final Field fieldBiomes; + + private static final MethodHandle methodGetVisibleChunk; + + private static final Field fieldThreadingDetector; + private static final Field fieldLock; + + private static final MethodHandle methodRemoveGameEventListener; + private static final MethodHandle methodremoveTickingBlockEntity; + + private static final Field fieldRemove; + + private static final Logger LOGGER = LogManagerCompat.getLogger(); + + private static Field SERVER_LEVEL_ENTITY_MANAGER; + + static final MethodHandle PALETTED_CONTAINER_GET; + + static { + final MethodHandles.Lookup lookup = MethodHandles.lookup(); + try { + fieldData = PalettedContainer.class.getDeclaredField(Refraction.pickName("data", "b")); + fieldData.setAccessible(true); + + Class dataClazz = fieldData.getType(); + dataConstructor = dataClazz.getDeclaredConstructors()[0]; + dataConstructor.setAccessible(true); + + fieldStorage = dataClazz.getDeclaredField(Refraction.pickName("storage", "b")); + fieldStorage.setAccessible(true); + fieldPalette = dataClazz.getDeclaredField(Refraction.pickName("palette", "c")); + fieldPalette.setAccessible(true); + + //noinspection JavaLangInvokeHandleSignature - method is obfuscated + palettedContainerUnpackSpigot = PaperLib.isPaper() ? null : lookup.findStatic( + PalettedContainer.class, + "a", // unpack + MethodType.methodType(DataResult.class, Strategy.class, PalettedContainerRO.PackedData.class) + ); + + fieldTickingFluidCount = LevelChunkSection.class.getDeclaredField(Refraction.pickName( + "tickingFluidCount", + "g" + )); + fieldTickingFluidCount.setAccessible(true); + fieldTickingBlockCount = LevelChunkSection.class.getDeclaredField(Refraction.pickName("tickingBlockCount", "f")); + fieldTickingBlockCount.setAccessible(true); + Field tmpFieldBiomes; + try { + // Seems it's sometimes biomes and sometimes "i". Idk this is just easier than having to try to deal with it + tmpFieldBiomes = LevelChunkSection.class.getDeclaredField("biomes"); // apparently unobf + } catch (NoSuchFieldException ignored) { + tmpFieldBiomes = LevelChunkSection.class.getDeclaredField("i"); // apparently obf + } + fieldBiomes = tmpFieldBiomes; + fieldBiomes.setAccessible(true); + + Method getVisibleChunkIfPresent = ChunkMap.class.getDeclaredMethod( + Refraction.pickName( + "getVisibleChunkIfPresent", + "b" + ), long.class + ); + getVisibleChunkIfPresent.setAccessible(true); + methodGetVisibleChunk = lookup.unreflect(getVisibleChunkIfPresent); + + if (!PaperLib.isPaper()) { + fieldThreadingDetector = PalettedContainer.class.getDeclaredField(Refraction.pickName("threadingDetector", "d")); + fieldThreadingDetector.setAccessible(true); + fieldLock = ThreadingDetector.class.getDeclaredField(Refraction.pickName("lock", "c")); + fieldLock.setAccessible(true); + SERVER_LEVEL_ENTITY_MANAGER = ServerLevel.class.getDeclaredField(Refraction.pickName("entityManager", "M")); + SERVER_LEVEL_ENTITY_MANAGER.setAccessible(true); + } else { + // in paper, the used methods are synchronized properly + fieldThreadingDetector = null; + fieldLock = null; + } + + Method removeGameEventListener = LevelChunk.class.getDeclaredMethod( + Refraction.pickName("removeGameEventListener", "a"), + BlockEntity.class, + ServerLevel.class + ); + removeGameEventListener.setAccessible(true); + methodRemoveGameEventListener = lookup.unreflect(removeGameEventListener); + + Method removeBlockEntityTicker = LevelChunk.class.getDeclaredMethod( + Refraction.pickName( + "removeBlockEntityTicker", + "k" + ), BlockPos.class + ); + removeBlockEntityTicker.setAccessible(true); + methodremoveTickingBlockEntity = lookup.unreflect(removeBlockEntityTicker); + + fieldRemove = BlockEntity.class.getDeclaredField(Refraction.pickName("remove", "p")); + fieldRemove.setAccessible(true); + + Method palettedContainerGet = PalettedContainer.class.getDeclaredMethod( + Refraction.pickName("get", "a"), + int.class + ); + palettedContainerGet.setAccessible(true); + PALETTED_CONTAINER_GET = lookup.unreflect(palettedContainerGet); + } catch (RuntimeException | Error e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static LinValueOutput createOutput() { + return LinValueOutput.createWithContext( + ProblemReporter.DISCARDING, + DedicatedServer.getServer().registryAccess() + ); + } + + public static LinValueInput createInput(LinCompoundTag input) { + return LinValueInput.create( + ProblemReporter.DISCARDING, + DedicatedServer.getServer().registryAccess(), + input + ); + } + + static boolean setSectionAtomic( + String worldName, + IntPair pair, + LevelChunkSection[] sections, + LevelChunkSection expected, + LevelChunkSection value, + int layer + ) { + return NMSAdapter.setSectionAtomic(worldName, pair, sections, expected, value, layer); + } + + // There is no point in having a functional semaphore for paper servers. + private static final ThreadLocal SEMAPHORE_THREAD_LOCAL = + ThreadLocal.withInitial(() -> new DelegateSemaphore(1, null)); + + static DelegateSemaphore applyLock(LevelChunkSection section) { + if (PaperLib.isPaper()) { + return SEMAPHORE_THREAD_LOCAL.get(); + } + try { + synchronized (section) { + PalettedContainer blocks = section.getStates(); + ThreadingDetector currentThreadingDetector = (ThreadingDetector) fieldThreadingDetector.get(blocks); + synchronized (currentThreadingDetector) { + Semaphore currentLock = (Semaphore) fieldLock.get(currentThreadingDetector); + if (currentLock instanceof DelegateSemaphore delegateSemaphore) { + return delegateSemaphore; + } + DelegateSemaphore newLock = new DelegateSemaphore(1, currentLock); + fieldLock.set(currentThreadingDetector, newLock); + return newLock; + } + } + } catch (Throwable e) { + LOGGER.error("Error apply DelegateSemaphore", e); + throw new RuntimeException(e); + } + } + + public static CompletableFuture ensureLoaded(ServerLevel serverLevel, int chunkX, int chunkZ) { + LevelChunk levelChunk = getChunkImmediatelyAsync(serverLevel, chunkX, chunkZ); + if (levelChunk != null) { + return CompletableFuture.completedFuture(levelChunk); + } + if (PaperLib.isPaper()) { + CompletableFuture future = serverLevel + .getWorld() + .getChunkAtAsync(chunkX, chunkZ, true, true) + .thenApply(chunk -> { + addTicket(serverLevel, chunkX, chunkZ); + try { + return toLevelChunk(chunk); + } catch (Throwable e) { + LOGGER.error("Could not asynchronously load chunk at {},{}", chunkX, chunkZ, e); + return null; + } + }); + try { + if (!future.isCompletedExceptionally() || (future.isDone() && future.get() != null)) { + return future; + } + Throwable t = future.exceptionNow(); + LOGGER.error("Asynchronous chunk load at {},{} exceptionally completed immediately", chunkX, chunkZ, t); + } catch (InterruptedException | ExecutionException e) { + LOGGER.error( + "Unexpected error when getting completed future at chunk {},{}. Returning to default.", + chunkX, + chunkZ, + e + ); + } + } + return CompletableFuture.supplyAsync(() -> TaskManager.taskManager().sync(() -> serverLevel.getChunk(chunkX, chunkZ))); + } + + private static LevelChunk toLevelChunk(Chunk chunk) { + return (LevelChunk) ((CraftChunk) chunk).getHandle(ChunkStatus.FULL); + } + + public static @Nullable LevelChunk getChunkImmediatelyAsync(ServerLevel serverLevel, int chunkX, int chunkZ) { + if (!PaperLib.isPaper()) { + LevelChunk nmsChunk = serverLevel.getChunkSource().getChunk(chunkX, chunkZ, false); + if (nmsChunk != null) { + return nmsChunk; + } + if (Fawe.isMainThread()) { + return serverLevel.getChunk(chunkX, chunkZ); + } + return null; + } else { + LevelChunk nmsChunk = serverLevel.getChunkSource().getChunkAtIfCachedImmediately(chunkX, chunkZ); + if (nmsChunk != null) { + addTicket(serverLevel, chunkX, chunkZ); + return nmsChunk; + } + nmsChunk = serverLevel.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ); + if (nmsChunk != null) { + addTicket(serverLevel, chunkX, chunkZ); + return nmsChunk; + } + // Avoid "async" methods from the main thread. + if (Fawe.isMainThread()) { + return serverLevel.getChunk(chunkX, chunkZ); + } + return null; + } + } + + private static void addTicket(ServerLevel serverLevel, int chunkX, int chunkZ) { + if (FoliaUtil.isFoliaServer()) { + try { + serverLevel.getChunkSource().addTicketWithRadius( + ChunkHolderManager.UNLOAD_COOLDOWN, + new ChunkPos(chunkX, chunkZ), + 0 + ); + } catch (Exception ignored) { + } + return; + } + // Ensure chunk is definitely loaded before applying a ticket + io.papermc.paper.util.MCUtil.MAIN_EXECUTOR.execute(() -> serverLevel + .getChunkSource() + .addTicketWithRadius(ChunkHolderManager.UNLOAD_COOLDOWN, new ChunkPos(chunkX, chunkZ), 0)); + } + + public static ChunkHolder getPlayerChunk(ServerLevel nmsWorld, final int chunkX, final int chunkZ) { + ChunkMap chunkMap = nmsWorld.getChunkSource().chunkMap; + try { + return (ChunkHolder) methodGetVisibleChunk.invoke(chunkMap, ChunkPos.pack(chunkX, chunkZ)); + } catch (Throwable thr) { + throw new RuntimeException(thr); + } + } + + @SuppressWarnings("deprecation") + public static void sendChunk(IntPair pair, ServerLevel nmsWorld, int chunkX, int chunkZ) { + ChunkHolder chunkHolder = getPlayerChunk(nmsWorld, chunkX, chunkZ); + if (chunkHolder == null) { + return; + } + LevelChunk levelChunk; + if (PaperLib.isPaper()) { + // getChunkAtIfLoadedImmediately is paper only + levelChunk = nmsWorld.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ); + } else { + levelChunk = chunkHolder.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK).orElse(null); + } + if (levelChunk == null) { + return; + } + StampLockHolder lockHolder = new StampLockHolder(); + NMSAdapter.beginChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder); + if (lockHolder.chunkLock == null) { + return; + } + if (FoliaUtil.isFoliaServer()) { + Bukkit.getServer().getRegionScheduler().execute( + WorldEditPlugin.getInstance(), + nmsWorld.getWorld(), + chunkX, + chunkZ, + () -> { + try { + LevelChunk regionChunk = nmsWorld.getChunkSource().getChunkAtIfLoadedImmediately(chunkX, chunkZ); + if (regionChunk == null) { + return; + } + ChunkPos pos = regionChunk.getPos(); + ClientboundLevelChunkWithLightPacket packet; + if (PaperLib.isPaper()) { + packet = new ClientboundLevelChunkWithLightPacket( + regionChunk, + nmsWorld.getLightEngine(), + null, + null, + false + ); + } else { + packet = new ClientboundLevelChunkWithLightPacket( + regionChunk, + nmsWorld.getLightEngine(), + null, + null + ); + } + nearbyPlayers(nmsWorld, pos).forEach(p -> p.connection.send(packet)); + } catch (IllegalStateException e) { + LOGGER.warn( + "Skipped sending chunk packet for chunk [{}, {}] due to concurrent section modification", + chunkX, + chunkZ, + e + ); + } finally { + NMSAdapter.endChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder); + } + } + ); + } else { + MinecraftServer.getServer().execute(() -> { + try { + ChunkPos pos = levelChunk.getPos(); + ClientboundLevelChunkWithLightPacket packet; + if (PaperLib.isPaper()) { + packet = new ClientboundLevelChunkWithLightPacket( + levelChunk, + nmsWorld.getLightEngine(), + null, + null, + false // last false is to not bother with x-ray + ); + } else { + // deprecated on paper - deprecation suppressed + packet = new ClientboundLevelChunkWithLightPacket( + levelChunk, + nmsWorld.getLightEngine(), + null, + null + ); + } + nearbyPlayers(nmsWorld, pos).forEach(p -> p.connection.send(packet)); + } finally { + NMSAdapter.endChunkPacketSend(nmsWorld.getWorld().getName(), pair, lockHolder); + } + }); + } + } + + private static List nearbyPlayers(ServerLevel serverLevel, ChunkPos coordIntPair) { + return serverLevel.getChunkSource().chunkMap.getPlayers(coordIntPair, false); + } + + /* + NMS conversion + */ + public static LevelChunkSection newChunkSection( + final int layer, + final char[] blocks, + CachedBukkitAdapter adapter, + RegistryAccess registryAccess, + Strategy strategy, + @Nullable PalettedContainer> biomes + ) { + return newChunkSection(layer, null, blocks, adapter, registryAccess, strategy, biomes); + } + + public static LevelChunkSection newChunkSection( + final int layer, + final IntFunction get, + char[] set, + CachedBukkitAdapter adapter, + RegistryAccess registryAccess, + Strategy strategy, + @Nullable PalettedContainer> biomes + ) { + if (set == null) { + return newChunkSection(registryAccess, biomes); + } + final int[] blockToPalette = FaweCache.INSTANCE.BLOCK_TO_PALETTE.get(); + final int[] paletteToBlock = FaweCache.INSTANCE.PALETTE_TO_BLOCK.get(); + final long[] blockStates = FaweCache.INSTANCE.BLOCK_STATES.get(); + final int[] blocksCopy = FaweCache.INSTANCE.SECTION_BLOCKS.get(); + try { + int num_palette; + if (get == null) { + num_palette = createPalette(blockToPalette, paletteToBlock, blocksCopy, set, adapter, true); + } else { + num_palette = createPalette(layer, blockToPalette, paletteToBlock, blocksCopy, get, set, adapter, true); + } + + boolean singleValue = num_palette == 1; + LongStream bits; + if (singleValue) { + bits = null; + } else { + int bitsPerEntry = Mth.ceillog2(num_palette); + if (bitsPerEntry < 4) { + bitsPerEntry = 4; + } + final int blockBitArrayEnd = MathMan.longArrayLength(bitsPerEntry, 4096); + final BitArrayUnstretched bitArray = new BitArrayUnstretched(bitsPerEntry, 4096, blockStates); + + bitArray.fromRaw(blocksCopy); + bits = Arrays.stream(blockStates, 0, blockBitArrayEnd); + } + + List palette = new ArrayList<>(); + for (int i = 0; i < num_palette; i++) { + int ordinal = paletteToBlock[i]; + PaperweightBlockMaterial material = (PaperweightBlockMaterial) BlockTypesCache.states[ordinal].getMaterial(); + palette.add(material.getState()); + } + + // Create palette with data + var packedData = new PalettedContainerRO.PackedData<>(palette, Optional.ofNullable(bits)); + DataResult> result; + if (PaperLib.isPaper()) { + result = PalettedContainer.unpack(strategy, packedData, Blocks.AIR.defaultBlockState(), null); + } else { + //noinspection unchecked + result = (DataResult>) + palettedContainerUnpackSpigot.invokeExact(strategy, packedData); + } + if (biomes == null) { + biomes = PalettedContainerFactory.create(registryAccess).createForBiomes(); + } + return new LevelChunkSection(result.getOrThrow(), biomes); + } catch (Throwable e) { + throw new RuntimeException("Failed to create block palette", e); + } finally { + Arrays.fill(blockToPalette, Integer.MAX_VALUE); + Arrays.fill(paletteToBlock, Integer.MAX_VALUE); + Arrays.fill(blockStates, 0); + Arrays.fill(blocksCopy, 0); + } + } + + @SuppressWarnings("deprecation") // Only deprecated in paper + private static LevelChunkSection newChunkSection( + RegistryAccess registryAccess, + @Nullable PalettedContainer> biomes + ) { + PalettedContainerFactory factory = PalettedContainerFactory.create(registryAccess); + if (biomes == null) { + return new LevelChunkSection(factory); + } + return new LevelChunkSection(factory.createForBlockStates(), biomes); + } + + public static void setBiomesToChunkSection(LevelChunkSection section, PalettedContainer> biomes) { + try { + fieldBiomes.set(section, biomes); + } catch (IllegalAccessException e) { + LOGGER.error("Could not set biomes to chunk section", e); + } + } + + /** + * Create a new {@link PalettedContainer}. Should only be used if no biome container existed beforehand. + */ + public static PalettedContainer> getBiomePalettedContainer( + BiomeType[] biomes, + IdMap> biomeRegistry + ) { + if (biomes == null) { + return null; + } + BukkitImplAdapter adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter(); + // Don't stream this as typically will see 1-4 biomes; stream overhead is large for the small length + final List> palette = new ArrayList<>(); + for (BiomeType biomeType : new LinkedList<>(Set.of(biomes))) { + if (biomeType == null) { + palette.add(biomeRegistry.byId(adapter.getInternalBiomeId(BiomeTypes.PLAINS))); + continue; + } + palette.add(biomeRegistry.byId(adapter.getInternalBiomeId(biomeType))); + } + int biomeCount = palette.size(); + int bitsPerEntry = MathMan.log2nlz(biomeCount - 1); + + if (bitsPerEntry > 3) { + bitsPerEntry = MathMan.log2nlz(biomeRegistry.size() - 1); + } + + int bitsPerEntryNonZero = Math.max(bitsPerEntry, 1); // We do want to use zero sometimes + final int arrayLength = MathMan.longArrayLength(bitsPerEntryNonZero, 64); + + var strategy = Strategy.createForBiomes(biomeRegistry); + var packedData = new PalettedContainerRO.PackedData<>( + palette, Optional.of(LongStream.of(new long[arrayLength])), bitsPerEntry + ); + DataResult>> result; + if (PaperLib.isPaper()) { + result = PalettedContainer.unpack( + strategy, + packedData, + biomeRegistry.byIdOrThrow(adapter.getInternalBiomeId(BiomeTypes.PLAINS)), + null + ); + } else { + try { + //noinspection unchecked + result = (DataResult>>) + palettedContainerUnpackSpigot.invokeExact(strategy, packedData); + } catch (Throwable e) { + throw new RuntimeException("Failed to create biome palette for Spigot", e); + } + } + var biomePalettedContainer = result.getOrThrow(); + + int index = 0; + for (int y = 0; y < 4; y++) { + for (int z = 0; z < 4; z++) { + for (int x = 0; x < 4; x++, index++) { + BiomeType biomeType = biomes[index]; + if (biomeType == null) { + continue; + } + Holder biome = biomeRegistry.byId(WorldEditPlugin + .getInstance() + .getBukkitImplAdapter() + .getInternalBiomeId(biomeType)); + if (biome == null) { + continue; + } + biomePalettedContainer.set(x, y, z, biome); + } + } + } + + return biomePalettedContainer; + } + + public static void clearCounts(final LevelChunkSection section) throws IllegalAccessException { + fieldTickingFluidCount.setShort(section, (short) 0); + fieldTickingBlockCount.setShort(section, (short) 0); + } + + public static BiomeType adapt(Holder biome, LevelAccessor levelAccessor) { + final Registry biomeRegistry = levelAccessor.registryAccess().lookupOrThrow(BIOME); + final int id = biomeRegistry.getId(biome.value()); + if (id < 0) { + // this shouldn't be the case, but other plugins can be weird + return BiomeTypes.OCEAN; + } + return BiomeTypes.getLegacy(id); + } + + static void removeBeacon(BlockEntity beacon, LevelChunk levelChunk) { + try { + if (levelChunk.loaded || levelChunk.level.isClientSide()) { + BlockEntity blockEntity = levelChunk.blockEntities.remove(beacon.getBlockPos()); + if (blockEntity != null) { + if (!levelChunk.level.isClientSide()) { + methodRemoveGameEventListener.invoke(levelChunk, beacon, levelChunk.level); + } + fieldRemove.set(beacon, true); + } + } + methodremoveTickingBlockEntity.invoke(levelChunk, beacon.getBlockPos()); + } catch (Throwable throwable) { + LOGGER.error("Error removing beacon", throwable); + } + } + + static List getEntities(LevelChunk chunk) { + if (PaperLib.isPaper()) { + return Optional.ofNullable(chunk.level + .moonrise$getEntityLookup() + .getChunk(chunk.locX, chunk.locZ)).map(ChunkEntitySlices::getAllEntities).orElse(Collections.emptyList()); + } + try { + //noinspection unchecked + return getEntitySectionManager(chunk.level).getEntities(chunk.getPos()); + } catch (IllegalAccessException e) { + throw new RuntimeException("Failed to lookup entities [PAPER=false]", e); + } + } + + /** + * Spigot only + */ + static PersistentEntitySectionManager getEntitySectionManager(ServerLevel level) throws IllegalAccessException { + //noinspection unchecked + return (PersistentEntitySectionManager) (SERVER_LEVEL_ENTITY_MANAGER.get(level)); + } + +} diff --git a/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightPostProcessor.java b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightPostProcessor.java new file mode 100644 index 0000000000..4dfe6a8916 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightPostProcessor.java @@ -0,0 +1,176 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1; + +import com.fastasyncworldedit.core.configuration.Settings; +import com.fastasyncworldedit.core.extent.processor.ProcessorScope; +import com.fastasyncworldedit.core.queue.IBatchProcessor; +import com.fastasyncworldedit.core.queue.IChunk; +import com.fastasyncworldedit.core.queue.IChunkGet; +import com.fastasyncworldedit.core.queue.IChunkSet; +import com.fastasyncworldedit.core.registry.state.PropertyKey; +import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1.PaperweightGetBlocks_Copy; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockTypes; +import com.sk89q.worldedit.world.block.BlockTypesCache; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.material.Fluids; + +import javax.annotation.Nullable; + +public class PaperweightPostProcessor implements IBatchProcessor { + + @Override + public IChunkSet processSet(final IChunk chunk, final IChunkGet get, final IChunkSet set) { + return set; + } + + @SuppressWarnings("deprecation") + @Override + public void postProcess(final IChunk chunk, final IChunkGet iChunkGet, final IChunkSet iChunkSet) { + boolean tickFluid = Settings.settings().EXPERIMENTAL.ALLOW_TICK_FLUIDS; + // The PostProcessor shouldn't be added, but just in case + if (!tickFluid) { + return; + } + PaperweightGetBlocks_Copy getBlocks = (PaperweightGetBlocks_Copy) iChunkGet; + layer: + for (int layer = iChunkSet.getMinSectionPosition(); layer <= iChunkSet.getMaxSectionPosition(); layer++) { + char[] set = iChunkSet.loadIfPresent(layer); + if (set == null) { + // No edit means no need to process + continue; + } + char[] get = null; + for (int i = 0; i < 4096; i++) { + char ordinal = set[i]; + char replacedOrdinal = BlockTypesCache.ReservedIDs.__RESERVED__; + boolean fromGet = false; // Used for liquids + if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) { + if (get == null) { + get = getBlocks.load(layer); + } + // If this is null, then it's because we're loading a layer in the range of 0->15, but blocks aren't + // actually being set + if (get == null) { + continue layer; + } + fromGet = true; + ordinal = replacedOrdinal = get[i]; + } + if (ordinal == BlockTypesCache.ReservedIDs.__RESERVED__) { + continue; + } else if (!fromGet) { // if fromGet, don't do the same again + if (get == null) { + get = getBlocks.load(layer); + } + replacedOrdinal = get[i]; + } + boolean ticking = BlockTypesCache.ticking[ordinal]; + boolean replacedWasTicking = BlockTypesCache.ticking[replacedOrdinal]; + boolean replacedWasLiquid = false; + BlockState replacedState = null; + if (!ticking) { + // If the block being replaced was not ticking, it cannot be a liquid + if (!replacedWasTicking) { + continue; + } + // If the block being replaced is not fluid, we do not need to worry + if (!(replacedWasLiquid = + (replacedState = BlockState.getFromOrdinal(replacedOrdinal)).getMaterial().isLiquid())) { + continue; + } + } + BlockState state = BlockState.getFromOrdinal(ordinal); + boolean liquid = state.getMaterial().isLiquid(); + int x = i & 15; + int y = (i >> 8) & 15; + int z = (i >> 4) & 15; + BlockPos position = new BlockPos((chunk.getX() << 4) + x, (layer << 4) + y, (chunk.getZ() << 4) + z); + if (liquid || replacedWasLiquid) { + if (liquid) { + addFluid(getBlocks.serverLevel, state, position); + continue; + } + // If the replaced fluid (is?) adjacent to water. Do not bother to check adjacent chunks(sections) as this + // may be time consuming. Chances are any fluid blocks in adjacent chunks are being replaced or will end up + // being ticked anyway. We only need it to be "hit" once. + if (!wasAdjacentToWater(get, set, i, x, y, z)) { + continue; + } + addFluid(getBlocks.serverLevel, replacedState, position); + } + } + } + } + + @Nullable + @Override + public Extent construct(final Extent child) { + throw new UnsupportedOperationException("Processing only"); + } + + @Override + public ProcessorScope getScope() { + return ProcessorScope.READING_BLOCKS; + } + + private boolean wasAdjacentToWater(char[] get, char[] set, int i, int x, int y, int z) { + if (set == null || get == null) { + return false; + } + char ordinal; + char reserved = BlockTypesCache.ReservedIDs.__RESERVED__; + if (x > 0 && set[i - 1] != reserved) { + if (BlockTypesCache.ticking[(ordinal = get[i - 1])] && isFluid(ordinal)) { + return true; + } + } + if (x < 15 && set[i + 1] != reserved) { + if (BlockTypesCache.ticking[(ordinal = get[i + 1])] && isFluid(ordinal)) { + return true; + } + } + if (z > 0 && set[i - 16] != reserved) { + if (BlockTypesCache.ticking[(ordinal = get[i - 16])] && isFluid(ordinal)) { + return true; + } + } + if (z < 15 && set[i + 16] != reserved) { + if (BlockTypesCache.ticking[(ordinal = get[i + 16])] && isFluid(ordinal)) { + return true; + } + } + if (y > 0 && set[i - 256] != reserved) { + if (BlockTypesCache.ticking[(ordinal = get[i - 256])] && isFluid(ordinal)) { + return true; + } + } + if (y < 15 && set[i + 256] != reserved) { + return BlockTypesCache.ticking[(ordinal = get[i + 256])] && isFluid(ordinal); + } + return false; + } + + @SuppressWarnings("deprecation") + private boolean isFluid(char ordinal) { + return BlockState.getFromOrdinal(ordinal).getMaterial().isLiquid(); + } + + @SuppressWarnings("deprecation") + private void addFluid(final ServerLevel serverLevel, final BlockState replacedState, final BlockPos position) { + Fluid type; + if (replacedState.getBlockType() == BlockTypes.LAVA) { + type = (int) replacedState.getState(PropertyKey.LEVEL) == 0 ? Fluids.LAVA : Fluids.FLOWING_LAVA; + } else { + type = (int) replacedState.getState(PropertyKey.LEVEL) == 0 ? Fluids.WATER : Fluids.FLOWING_WATER; + } + serverLevel.scheduleTick( + position, + type, + type.getTickDelay(serverLevel) + ); + } + +} diff --git a/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightStarlightRelighter.java b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightStarlightRelighter.java new file mode 100644 index 0000000000..f45530162f --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightStarlightRelighter.java @@ -0,0 +1,81 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1; + +import com.fastasyncworldedit.bukkit.adapter.StarlightRelighter; +import com.fastasyncworldedit.core.configuration.Settings; +import com.fastasyncworldedit.core.math.IntPair; +import com.fastasyncworldedit.core.queue.IQueueExtent; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.TicketType; +import net.minecraft.util.Unit; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.status.ChunkPyramid; +import net.minecraft.world.level.chunk.status.ChunkStatus; + +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import java.util.function.IntConsumer; + +public class PaperweightStarlightRelighter extends StarlightRelighter { + + private static final TicketType FAWE_TICKET = new TicketType<>( + TicketType.NO_TIMEOUT, TicketType.FLAG_LOADING + ); + private static final int LIGHT_LEVEL = ChunkMap.MAX_VIEW_DISTANCE + ChunkPyramid.LOADING_PYRAMID + .getStepTo(ChunkStatus.FULL) + .getAccumulatedRadiusOf(ChunkStatus.LIGHT); + + public PaperweightStarlightRelighter(ServerLevel serverLevel, IQueueExtent queue) { + super(serverLevel, queue); + } + + @Override + protected ChunkPos createChunkPos(final long chunkKey) { + return ChunkPos.unpack(chunkKey); + } + + @Override + protected long asLong(final int chunkX, final int chunkZ) { + return ChunkPos.pack(chunkX, chunkZ); + } + + @Override + protected CompletableFuture chunkLoadFuture(final ChunkPos chunkPos) { + return serverLevel.getWorld().getChunkAtAsync(chunkPos.x(), chunkPos.z()) + .thenAccept(c -> serverLevel.getChunkSource().addTicketAtLevel( + FAWE_TICKET, + chunkPos, + LIGHT_LEVEL + )); + } + + protected void invokeRelight( + Set coords, + Consumer chunkCallback, + IntConsumer processCallback + ) { + try { + serverLevel.getChunkSource().getLightEngine().starlight$serverRelightChunks(coords, chunkCallback, processCallback); + } catch (Exception e) { + LOGGER.error("Error occurred on relighting", e); + } + } + + /* + * Allow the server to unload the chunks again. + * Also, if chunk packets are sent delayed, we need to do that here + */ + protected void postProcessChunks(Set coords) { + boolean delay = Settings.settings().LIGHTING.DELAY_PACKET_SENDING; + for (ChunkPos pos : coords) { + int x = pos.x(); + int z = pos.z(); + if (delay) { // we still need to send the block changes of that chunk + PaperweightPlatformAdapter.sendChunk(new IntPair(x, z), serverLevel, x, z); + } + serverLevel.getChunkSource().removeTicketAtLevel(FAWE_TICKET, pos, LIGHT_LEVEL); + } + } + +} diff --git a/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightStarlightRelighterFactory.java b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightStarlightRelighterFactory.java new file mode 100644 index 0000000000..e09ba4183a --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/PaperweightStarlightRelighterFactory.java @@ -0,0 +1,25 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1; + +import com.fastasyncworldedit.core.extent.processor.lighting.NullRelighter; +import com.fastasyncworldedit.core.extent.processor.lighting.RelightMode; +import com.fastasyncworldedit.core.extent.processor.lighting.Relighter; +import com.fastasyncworldedit.core.extent.processor.lighting.RelighterFactory; +import com.fastasyncworldedit.core.queue.IQueueExtent; +import com.sk89q.worldedit.world.World; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.CraftWorld; + +import javax.annotation.Nonnull; + +public class PaperweightStarlightRelighterFactory implements RelighterFactory { + + @Override + public @Nonnull Relighter createRelighter(RelightMode relightMode, World world, IQueueExtent queue) { + org.bukkit.World w = Bukkit.getWorld(world.getName()); + if (w == null) { + return NullRelighter.INSTANCE; + } + return new PaperweightStarlightRelighter(((CraftWorld) w).getHandle(), queue); + } + +} diff --git a/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/regen/PaperweightRegen.java b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/regen/PaperweightRegen.java new file mode 100644 index 0000000000..4cfda86891 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/fawe/v26_1/regen/PaperweightRegen.java @@ -0,0 +1,322 @@ +package com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1.regen; + +import com.fastasyncworldedit.bukkit.adapter.Regenerator; +import com.fastasyncworldedit.core.Fawe; +import com.fastasyncworldedit.core.queue.IChunkCache; +import com.fastasyncworldedit.core.queue.IChunkGet; +import com.fastasyncworldedit.core.queue.implementation.chunk.ChunkCache; +import com.fastasyncworldedit.core.util.FoliaUtil; +import com.google.common.collect.ImmutableList; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import com.sk89q.worldedit.bukkit.adapter.Refraction; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.util.io.file.SafeFiles; +import com.sk89q.worldedit.world.RegenOptions; +import io.papermc.lib.PaperLib; +import io.papermc.paper.world.PaperWorldLoader; +import io.papermc.paper.world.saveddata.PaperWorldPDC; +import net.minecraft.core.Holder; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.dedicated.DedicatedServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.ProgressListener; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator; +import net.minecraft.world.level.levelgen.WorldGenSettings; +import net.minecraft.world.level.levelgen.WorldOptions; +import net.minecraft.world.level.storage.LevelResource; +import net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraft.world.level.storage.SavedDataStorage; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer; +import org.bukkit.generator.BiomeProvider; + +import javax.annotation.Nonnull; +import java.lang.reflect.Field; +import java.nio.file.Path; +import java.util.Map; +import java.util.OptionalLong; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.function.BooleanSupplier; +import java.util.function.Supplier; + +import static net.minecraft.core.registries.Registries.BIOME; + +public class PaperweightRegen extends Regenerator { + + private static final String REGEN_WORLD_NAME = "faweregentempworld"; + + private static final Field serverWorldsField; + private static final Field paperConfigField; + private static final Field generatorSettingBaseSupplierField; + + + static { + try { + serverWorldsField = CraftServer.class.getDeclaredField("worlds"); + serverWorldsField.setAccessible(true); + + Field tmpPaperConfigField; + try { //only present on paper + tmpPaperConfigField = Level.class.getDeclaredField("paperConfig"); + tmpPaperConfigField.setAccessible(true); + } catch (Exception e) { + tmpPaperConfigField = null; + } + paperConfigField = tmpPaperConfigField; + + generatorSettingBaseSupplierField = NoiseBasedChunkGenerator.class.getDeclaredField(Refraction.pickName( + "settings", "e")); + generatorSettingBaseSupplierField.setAccessible(true); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + //runtime + private ServerLevel originalServerWorld; + private ServerLevel freshWorld; + private LevelStorageSource.LevelStorageAccess session; + + private Path tempDir; + + public PaperweightRegen( + World originalBukkitWorld, + Region region, + Extent target, + RegenOptions options + ) { + super(originalBukkitWorld, region, target, options); + } + + @Override + protected void runTasks(final BooleanSupplier shouldKeepTicking) { + while (shouldKeepTicking.getAsBoolean()) { + if (!this.freshWorld.getChunkSource().pollTask()) { + return; + } + } + } + + @Override + protected boolean prepare() { + this.originalServerWorld = ((CraftWorld) originalBukkitWorld).getHandle(); + seed = options.getSeed().orElse(originalServerWorld.getSeed()); + return true; + } + + @Override + protected boolean initNewWorld() throws Exception { + if (!PaperLib.isPaper()) { + throw new UnsupportedOperationException("Regen requires Paper"); + } + + //world folder + tempDir = java.nio.file.Files.createTempDirectory("FastAsyncWorldEditWorldGen"); + + //prepare for world init (see upstream implementation for reference) + World.Environment environment = originalBukkitWorld.getEnvironment(); + org.bukkit.generator.ChunkGenerator generator = originalBukkitWorld.getGenerator(); + LevelStorageSource levelStorageSource = LevelStorageSource.createDefault(tempDir); + ResourceKey levelStemResourceKey = getWorldDimKey(environment); + session = levelStorageSource.createAccess(REGEN_WORLD_NAME); + + MinecraftServer server = originalServerWorld.getCraftServer().getServer(); + WorldOptions originalOpts = originalServerWorld.worldGenSettings.options(); + WorldOptions newOpts = options.getSeed().isPresent() + ? originalOpts.withSeed(OptionalLong.of(seed)) + : originalOpts; + WorldGenSettings newWorldGenSettings = new WorldGenSettings( + newOpts, + originalServerWorld.worldGenSettings.dimensions() + ); + + PaperWorldLoader.LoadedWorldData loadedWorldData = new PaperWorldLoader.LoadedWorldData( + REGEN_WORLD_NAME, + UUID.randomUUID(), + new PaperWorldPDC((CraftPersistentDataContainer) originalBukkitWorld.getPersistentDataContainer()), + originalServerWorld.serverLevelData + ); + + BiomeProvider biomeProvider = getBiomeProvider(); + + SavedDataStorage savedDataStorage = new SavedDataStorage(session.getDimensionPath(originalServerWorld.dimension()) + .resolve(LevelResource.DATA.id()), server.getFixerUpper(), server.registryAccess()); + + //init world + freshWorld = Fawe.instance().getQueueHandler().sync((Supplier) () -> new ServerLevel( + server, + server.executor, + session, + newWorldGenSettings, + originalServerWorld.dimension(), + new LevelStem( + originalServerWorld.dimensionTypeRegistration(), + originalServerWorld.getChunkSource().getGenerator() + ), + originalServerWorld.isDebug(), + seed, + ImmutableList.of(), + false, + levelStemResourceKey, + environment, + generator, + biomeProvider, + savedDataStorage, + loadedWorldData + ) { + + private final Holder singleBiome = options.hasBiomeType() ? DedicatedServer.getServer().registryAccess() + .lookupOrThrow(BIOME).asHolderIdMap().byIdOrThrow( + WorldEditPlugin.getInstance().getBukkitImplAdapter().getInternalBiomeId(options.getBiomeType()) + ) : null; + + @Override + public @Nonnull Holder getUncachedNoiseBiome(int biomeX, int biomeY, int biomeZ) { + if (options.hasBiomeType()) { + return singleBiome; + } + return super.getUncachedNoiseBiome(biomeX, biomeY, biomeZ); + } + + @Override + public void save( + final ProgressListener progressListener, + final boolean flush, + final boolean savingDisabled + ) { + // noop, spigot + } + + @Override + public void save( + final ProgressListener progressListener, + final boolean flush, + final boolean savingDisabled, + final boolean close + ) { + // noop, paper + } + }).get(); + freshWorld.noSave = true; + removeWorldFromWorldsMap(); + if (paperConfigField != null) { + paperConfigField.set(freshWorld, originalServerWorld.paperConfig()); + } + if (FoliaUtil.isFoliaServer()) { + return initWorldForFolia(); + } + return true; + } + + private boolean initWorldForFolia() throws ExecutionException, InterruptedException { + MinecraftServer console = ((CraftServer) Bukkit.getServer()).getServer(); + ChunkPos spawnChunk = ChunkPos.containing( + freshWorld.getChunkSource().randomState().sampler().findSpawnPosition() + ); + + setRandomSpawnSelection(spawnChunk); + + CompletableFuture initFuture = new CompletableFuture<>(); + Bukkit.getServer().getRegionScheduler().run( + WorldEditPlugin.getInstance(), + freshWorld.getWorld(), + spawnChunk.x(), + spawnChunk.z(), + task -> { + try { + console.initWorld(freshWorld, null); + initFuture.complete(true); + } catch (Exception e) { + initFuture.completeExceptionally(e); + } + } + ); + + return initFuture.get(); + } + + private void setRandomSpawnSelection(ChunkPos spawnChunk) { + try { + Field randomSpawnField = ServerLevel.class.getDeclaredField("randomSpawnSelection"); + randomSpawnField.setAccessible(true); + randomSpawnField.set(freshWorld, spawnChunk); + } catch (ReflectiveOperationException e) { + throw new RuntimeException("Failed to set randomSpawnSelection for Folia world initialization", e); + } + } + + @Override + protected void cleanup() { + try { + session.close(); + } catch (Exception ignored) { + } + + //shutdown chunk provider + try { + Fawe.instance().getQueueHandler().sync(() -> { + try { + freshWorld.getChunkSource().getDataStorage().cache.clear(); + freshWorld.getChunkSource().close(false); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } catch (Exception ignored) { + } + + //remove world from server + try { + Fawe.instance().getQueueHandler().sync(this::removeWorldFromWorldsMap); + } catch (Exception ignored) { + } + + //delete directory + try { + SafeFiles.tryHardToDeleteDir(tempDir); + } catch (Exception ignored) { + } + } + + @Override + protected World getFreshWorld() { + return freshWorld != null ? freshWorld.getWorld() : null; + } + + @Override + protected IChunkCache initSourceQueueCache() { + return new ChunkCache<>(BukkitAdapter.adapt(freshWorld.getWorld())); + } + + //util + @SuppressWarnings("unchecked") + private void removeWorldFromWorldsMap() { + try { + Map map = (Map) serverWorldsField.get(Bukkit.getServer()); + map.remove(REGEN_WORLD_NAME); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + private ResourceKey getWorldDimKey(World.Environment env) { + return switch (env) { + case NETHER -> LevelStem.NETHER; + case THE_END -> LevelStem.END; + default -> LevelStem.OVERWORLD; + }; + } + +} diff --git a/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v26_1/ComponentConverter.java b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v26_1/ComponentConverter.java new file mode 100644 index 0000000000..736ed92181 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v26_1/ComponentConverter.java @@ -0,0 +1,77 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.bukkit.adapter.impl.v26_1; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.Strictness; +import com.google.gson.stream.JsonReader; +import com.mojang.serialization.JsonOps; +import net.minecraft.core.HolderLookup; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.ComponentSerialization; +import net.minecraft.network.chat.MutableComponent; + +import java.io.StringReader; +import javax.annotation.Nullable; + +public class ComponentConverter { + + public static class Serializer { + private static final Gson GSON = new GsonBuilder().disableHtmlEscaping().create(); + + private Serializer() { + } + + static MutableComponent deserialize(JsonElement json, HolderLookup.Provider registries) { + return (MutableComponent) ComponentSerialization.CODEC.parse(registries.createSerializationContext(JsonOps.INSTANCE), json).getOrThrow(JsonParseException::new); + } + + static JsonElement serialize(Component text, HolderLookup.Provider registries) { + return ComponentSerialization.CODEC.encodeStart(registries.createSerializationContext(JsonOps.INSTANCE), text).getOrThrow(JsonParseException::new); + } + + public static String toJson(Component text, HolderLookup.Provider registries) { + return GSON.toJson(serialize(text, registries)); + } + + @Nullable + public static MutableComponent fromJson(String json, HolderLookup.Provider registries) { + JsonElement jsonelement = JsonParser.parseString(json); + return jsonelement == null ? null : deserialize(jsonelement, registries); + } + + @Nullable + public static MutableComponent fromJson(@Nullable JsonElement json, HolderLookup.Provider registries) { + return json == null ? null : deserialize(json, registries); + } + + @Nullable + public static MutableComponent fromJsonLenient(String json, HolderLookup.Provider registries) { + JsonReader jsonreader = new JsonReader(new StringReader(json)); + jsonreader.setStrictness(Strictness.LENIENT); + JsonElement jsonelement = JsonParser.parseReader(jsonreader); + return jsonelement == null ? null : deserialize(jsonelement, registries); + } + } +} diff --git a/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v26_1/PaperweightAdapter.java b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v26_1/PaperweightAdapter.java new file mode 100644 index 0000000000..cb8386eeaa --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v26_1/PaperweightAdapter.java @@ -0,0 +1,1251 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.bukkit.adapter.impl.v26_1; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.google.common.util.concurrent.Futures; +import com.mojang.serialization.Codec; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.blocks.BaseItem; +import com.sk89q.worldedit.blocks.BaseItemStack; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter; +import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1.PaperweightFaweAdapter; +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.extension.platform.Watchdog; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.internal.Constants; +import com.sk89q.worldedit.internal.block.BlockStateIdAccess; +import com.sk89q.worldedit.internal.wna.WorldNativeAccess; +import com.sk89q.worldedit.math.BlockVector2; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.registry.state.Property; +import com.sk89q.worldedit.util.Direction; +import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.util.concurrency.LazyReference; +import com.sk89q.worldedit.util.formatting.text.Component; +import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; +import com.sk89q.worldedit.util.formatting.text.serializer.gson.GsonComponentSerializer; +import com.sk89q.worldedit.util.io.file.SafeFiles; +import com.sk89q.worldedit.world.DataFixer; +import com.sk89q.worldedit.world.RegenOptions; +import com.sk89q.worldedit.world.biome.BiomeCategory; +import com.sk89q.worldedit.world.biome.BiomeType; +import com.sk89q.worldedit.world.biome.BiomeTypes; +import com.sk89q.worldedit.world.block.BaseBlock; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockStateHolder; +import com.sk89q.worldedit.world.block.BlockType; +import com.sk89q.worldedit.world.block.BlockTypes; +import com.sk89q.worldedit.world.entity.EntityTypes; +import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; +import com.sk89q.worldedit.world.generation.StructureType; +import com.sk89q.worldedit.world.generation.TreeType; +import com.sk89q.worldedit.world.item.ItemType; +import io.papermc.lib.PaperLib; +import io.papermc.paper.world.PaperWorldLoader; +import io.papermc.paper.world.saveddata.PaperWorldPDC; +import net.minecraft.SharedConstants; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.HolderSet; +import net.minecraft.core.Registry; +import net.minecraft.core.SectionPos; +import net.minecraft.core.component.DataComponentPatch; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.ByteArrayTag; +import net.minecraft.nbt.ByteTag; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.DoubleTag; +import net.minecraft.nbt.EndTag; +import net.minecraft.nbt.FloatTag; +import net.minecraft.nbt.IntArrayTag; +import net.minecraft.nbt.IntTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.LongArrayTag; +import net.minecraft.nbt.LongTag; +import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.ShortTag; +import net.minecraft.nbt.StringTag; +import net.minecraft.nbt.Tag; +import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; +import net.minecraft.network.protocol.game.ClientboundEntityEventPacket; +import net.minecraft.resources.Identifier; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.dedicated.DedicatedServer; +import net.minecraft.server.level.ChunkResult; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.RandomSource; +import net.minecraft.util.Util; +import net.minecraft.util.thread.BlockableEventLoop; +import net.minecraft.world.Clearable; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntitySpawnReason; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.UseOnContext; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.StructureBlockEntity; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.chunk.ChunkAccess; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.PalettedContainer; +import net.minecraft.world.level.chunk.status.ChunkStatus; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import net.minecraft.world.level.levelgen.feature.CoralTreeFeature; +import net.minecraft.world.level.levelgen.feature.FallenTreeFeature; +import net.minecraft.world.level.levelgen.feature.TreeFeature; +import net.minecraft.world.level.levelgen.placement.PlacedFeature; +import net.minecraft.world.level.levelgen.structure.BoundingBox; +import net.minecraft.world.level.levelgen.structure.Structure; +import net.minecraft.world.level.levelgen.structure.StructureStart; +import net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraft.world.level.storage.TagValueInput; +import net.minecraft.world.level.storage.TagValueOutput; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.Vec3; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.World.Environment; +import org.bukkit.block.data.BlockData; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.block.data.CraftBlockData; +import org.bukkit.craftbukkit.entity.CraftEntity; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer; +import org.bukkit.entity.Player; +import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; +import org.bukkit.generator.ChunkGenerator; +import org.enginehub.linbus.common.LinTagId; +import org.enginehub.linbus.tree.LinByteArrayTag; +import org.enginehub.linbus.tree.LinByteTag; +import org.enginehub.linbus.tree.LinCompoundTag; +import org.enginehub.linbus.tree.LinDoubleTag; +import org.enginehub.linbus.tree.LinEndTag; +import org.enginehub.linbus.tree.LinFloatTag; +import org.enginehub.linbus.tree.LinIntArrayTag; +import org.enginehub.linbus.tree.LinIntTag; +import org.enginehub.linbus.tree.LinListTag; +import org.enginehub.linbus.tree.LinLongArrayTag; +import org.enginehub.linbus.tree.LinLongTag; +import org.enginehub.linbus.tree.LinShortTag; +import org.enginehub.linbus.tree.LinStringTag; +import org.enginehub.linbus.tree.LinTag; +import org.enginehub.linbus.tree.LinTagType; +import org.spigotmc.SpigotConfig; +import org.spigotmc.WatchdogThread; + +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.OptionalInt; +import java.util.Set; +import java.util.TreeMap; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +public final class PaperweightAdapter implements BukkitImplAdapter { + + private final Logger logger = Logger.getLogger(getClass().getCanonicalName()); + + private final Field serverWorldsField; + private final Method getChunkFutureMethod; + private final Field chunkProviderExecutorField; + private final PaperweightDataConverters dataFixer; + private final Watchdog watchdog; + + private static final RandomSource random = RandomSource.create(); + + private static final String WRONG_VERSION = + """ + This version of WorldEdit has not been tested with the current Minecraft version. + While it may work, there might be unexpected issues. + It is recommended to use a version of WorldEdit that supports your Minecraft version. + For more information, see https://worldedit.enginehub.org/en/latest/faq/#bukkit-adapters + """.stripIndent(); + + // ------------------------------------------------------------------------ + // Code that may break between versions of Minecraft + // ------------------------------------------------------------------------ + + public PaperweightAdapter() throws NoSuchFieldException, NoSuchMethodException { + // A simple test + @SuppressWarnings({"ReturnValueIgnored", "unused"}) + var unused = CraftServer.class.cast(Bukkit.getServer()); + + int dataVersion = SharedConstants.getCurrentVersion().dataVersion().version(); + if (dataVersion != Constants.DATA_VERSION_MC_26_1 && dataVersion != Constants.DATA_VERSION_MC_26_1_1) { + logger.warning(WRONG_VERSION); + } + + serverWorldsField = CraftServer.class.getDeclaredField("worlds"); + serverWorldsField.setAccessible(true); + + getChunkFutureMethod = ServerChunkCache.class.getDeclaredMethod( + StaticRefraction.GET_CHUNK_FUTURE_MAIN_THREAD, + int.class, int.class, ChunkStatus.class, boolean.class + ); + getChunkFutureMethod.setAccessible(true); + + chunkProviderExecutorField = ServerChunkCache.class.getDeclaredField( + StaticRefraction.MAIN_THREAD_PROCESSOR + ); + chunkProviderExecutorField.setAccessible(true); + + this.dataFixer = new PaperweightDataConverters(dataVersion, this); + + Watchdog watchdog; + try { + Class.forName("org.spigotmc.WatchdogThread"); + watchdog = new SpigotWatchdog(); + } catch (ClassNotFoundException | NoSuchFieldException e) { + try { + watchdog = new MojangWatchdog(((CraftServer) Bukkit.getServer()).getServer()); + } catch (NoSuchFieldException ex) { + watchdog = null; + } + } + this.watchdog = watchdog; + + try { + Class.forName("org.spigotmc.SpigotConfig"); + SpigotConfig.config.set("world-settings.worldeditregentempworld.verbose", false); + } catch (ClassNotFoundException ignored) { + // It's fine if we couldn't set it + } + } + + @Override + public DataFixer getDataFixer() { + return this.dataFixer; + } + + /** + * Read the given NBT data into the given tile entity. + * + * @param tileEntity the tile entity + * @param tag the tag + */ + static void readTagIntoTileEntity(net.minecraft.nbt.CompoundTag tag, BlockEntity tileEntity) { + PaperweightLoggingProblemReporter.with(() -> "loading tile entity at " + tileEntity.getBlockPos(), reporter -> { + tileEntity.loadWithComponents(TagValueInput.create(reporter, MinecraftServer.getServer().registryAccess(), tag)); + tileEntity.setChanged(); + return null; + }); + } + + /** + * Get the ID string of the given entity. + * + * @param entity the entity + * @return the entity ID + */ + private static String getEntityId(Entity entity) { + return EntityType.getKey(entity.getType()).toString(); + } + + /** + * Write the entity's NBT data to the given tag. + * + * @param entity the entity + * @param tag the tag + */ + private static boolean readEntityIntoTag(Entity entity, TagValueOutput tag) { + return entity.save(tag); + } + + private static Block getBlockFromType(BlockType blockType) { + return DedicatedServer.getServer().registryAccess().lookupOrThrow(Registries.BLOCK).getValue(Identifier.tryParse(blockType.id())); + } + + private static Item getItemFromType(ItemType itemType) { + return DedicatedServer.getServer().registryAccess().lookupOrThrow(Registries.ITEM).getValue(Identifier.tryParse(itemType.id())); + } + + @Override + public OptionalInt getInternalBlockStateId(BlockData data) { + net.minecraft.world.level.block.state.BlockState state = ((CraftBlockData) data).getState(); + int combinedId = Block.getId(state); + return combinedId == 0 && state.getBlock() != Blocks.AIR ? OptionalInt.empty() : OptionalInt.of(combinedId); + } + + @Override + public OptionalInt getInternalBlockStateId(BlockState state) { + Block mcBlock = getBlockFromType(state.getBlockType()); + net.minecraft.world.level.block.state.BlockState newState = mcBlock.defaultBlockState(); + Map, Object> states = state.getStates(); + newState = applyProperties(mcBlock.getStateDefinition(), newState, states); + final int combinedId = Block.getId(newState); + return combinedId == 0 && state.getBlockType() != BlockTypes.AIR ? OptionalInt.empty() : OptionalInt.of(combinedId); + } + + public BlockState adapt(net.minecraft.world.level.block.state.BlockState blockState) { + int internalId = Block.getId(blockState); + BlockState state = BlockStateIdAccess.getBlockStateById(internalId); + if (state == null) { + state = BukkitAdapter.adapt(CraftBlockData.createData(blockState)); + } + + return state; + } + + public BiomeType adapt(Biome biome) { + var mcBiome = ((CraftServer) Bukkit.getServer()).getServer().registryAccess().lookupOrThrow(Registries.BIOME).getKey(biome); + if (mcBiome == null) { + return null; + } + return BiomeType.REGISTRY.get(mcBiome.toString()); + } + + public net.minecraft.world.level.block.state.BlockState adapt(BlockState blockState) { + int internalId = BlockStateIdAccess.getBlockStateId(blockState); + return Block.stateById(internalId); + } + + @Override + public BlockState getBlock(Location location) { + checkNotNull(location); + + CraftWorld craftWorld = ((CraftWorld) location.getWorld()); + int x = location.getBlockX(); + int y = location.getBlockY(); + int z = location.getBlockZ(); + + final ServerLevel handle = craftWorld.getHandle(); + LevelChunk chunk = handle.getChunk(x >> 4, z >> 4); + final BlockPos blockPos = new BlockPos(x, y, z); + final net.minecraft.world.level.block.state.BlockState blockData = chunk.getBlockState(blockPos); + return adapt(blockData); + } + + @Override + public BaseBlock getFullBlock(Location location) { + BlockState state = getBlock(location); + + CraftWorld craftWorld = ((CraftWorld) location.getWorld()); + int x = location.getBlockX(); + int y = location.getBlockY(); + int z = location.getBlockZ(); + + final ServerLevel handle = craftWorld.getHandle(); + LevelChunk chunk = handle.getChunk(x >> 4, z >> 4); + final BlockPos blockPos = new BlockPos(x, y, z); + + // Read the NBT data + BlockEntity te = chunk.getBlockEntity(blockPos); + if (te != null) { + net.minecraft.nbt.CompoundTag tag = PaperweightLoggingProblemReporter.with( + () -> "serializing block entity at " + blockPos, + reporter -> { + var tagValueOutput = TagValueOutput.createWithContext(reporter, MinecraftServer.getServer().registryAccess()); + te.saveWithId(tagValueOutput); + return tagValueOutput.buildResult(); + } + ); + return state.toBaseBlock(LazyReference.from(() -> (LinCompoundTag) toNativeLin(tag))); + } + + return state.toBaseBlock(); + } + + private static final HashMap> biomeTypeToNMSCache = new HashMap<>(); + private static final HashMap, BiomeType> biomeTypeFromNMSCache = new HashMap<>(); + + @Override + public BiomeType getBiome(Location location) { + checkNotNull(location); + + CraftWorld craftWorld = ((CraftWorld) location.getWorld()); + int x = location.getBlockX(); + int y = location.getBlockY(); + int z = location.getBlockZ(); + + final ServerLevel handle = craftWorld.getHandle(); + LevelChunk chunk = handle.getChunk(x >> 4, z >> 4); + + return biomeTypeFromNMSCache.computeIfAbsent(chunk.getNoiseBiome(x >> 2, y >> 2, z >> 2), b -> BiomeType.REGISTRY.get(b.unwrapKey().orElseThrow().identifier().toString())); + } + + @Override + public void setBiome(Location location, BiomeType biome) { + checkNotNull(location); + checkNotNull(biome); + + CraftWorld craftWorld = ((CraftWorld) location.getWorld()); + int x = location.getBlockX(); + int y = location.getBlockY(); + int z = location.getBlockZ(); + + final ServerLevel handle = craftWorld.getHandle(); + LevelChunk chunk = handle.getChunk(x >> 4, z >> 4); + var biomeArray = (PalettedContainer>) chunk.getSection(chunk.getSectionIndex(y)).getBiomes(); + biomeArray.getAndSetUnchecked( + (x >> 2) & 3, (y >> 2) & 3, (z >> 2) & 3, + biomeTypeToNMSCache.computeIfAbsent(biome, b -> craftWorld.getHandle().registryAccess().lookup(Registries.BIOME) + .orElseThrow() + .getOrThrow(ResourceKey.create(Registries.BIOME, Identifier.parse(b.id())))) + ); + chunk.markUnsaved(); + } + + @Override + public WorldNativeAccess createWorldNativeAccess(World world) { + return new PaperweightWorldNativeAccess(this, new WeakReference<>(((CraftWorld) world).getHandle())); + } + + private static net.minecraft.core.Direction adapt(Direction face) { + return switch (face) { + case NORTH -> net.minecraft.core.Direction.NORTH; + case SOUTH -> net.minecraft.core.Direction.SOUTH; + case WEST -> net.minecraft.core.Direction.WEST; + case EAST -> net.minecraft.core.Direction.EAST; + case DOWN -> net.minecraft.core.Direction.DOWN; + default -> net.minecraft.core.Direction.UP; + }; + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + private net.minecraft.world.level.block.state.BlockState applyProperties( + StateDefinition stateContainer, + net.minecraft.world.level.block.state.BlockState newState, + Map, Object> states + ) { + for (Map.Entry, Object> state : states.entrySet()) { + net.minecraft.world.level.block.state.properties.Property property = + stateContainer.getProperty(state.getKey().getName()); + Comparable value = (Comparable) state.getValue(); + // we may need to adapt this value, depending on the source prop + if (property instanceof net.minecraft.world.level.block.state.properties.EnumProperty) { + if (property.getValueClass() == net.minecraft.core.Direction.class) { + value = adapt((Direction) value); + } else { + String enumName = (String) value; + value = ((net.minecraft.world.level.block.state.properties.EnumProperty) property) + .getValue(enumName).orElseThrow(() -> + new IllegalStateException( + "Enum property " + property.getName() + " does not contain " + enumName + ) + ); + } + } + + newState = newState.setValue( + (net.minecraft.world.level.block.state.properties.Property) property, + (Comparable) value + ); + } + return newState; + } + + @Override + public BaseEntity getEntity(org.bukkit.entity.Entity entity) { + checkNotNull(entity); + + CraftEntity craftEntity = ((CraftEntity) entity); + Entity mcEntity = craftEntity.getHandle(); + + String id = getEntityId(mcEntity); + + net.minecraft.nbt.CompoundTag tag = PaperweightLoggingProblemReporter.with( + () -> "serializing entity " + mcEntity.getStringUUID(), + reporter -> { + var tagValueOutput = TagValueOutput.createWithContext(reporter, mcEntity.registryAccess()); + if (!readEntityIntoTag(mcEntity, tagValueOutput)) { + return null; + } + return tagValueOutput.buildResult(); + } + ); + if (tag == null) { + return null; + } + return new BaseEntity( + EntityTypes.get(id), + LazyReference.from(() -> (LinCompoundTag) toNativeLin(tag)) + ); + } + + @Nullable + @Override + public org.bukkit.entity.Entity createEntity(Location location, BaseEntity state) { + checkNotNull(location); + checkNotNull(state); + + CraftWorld craftWorld = ((CraftWorld) location.getWorld()); + ServerLevel worldServer = craftWorld.getHandle(); + + String entityId = state.getType().id(); + + LinCompoundTag nativeTag = state.getNbt(); + net.minecraft.nbt.CompoundTag tag; + if (nativeTag != null) { + tag = (net.minecraft.nbt.CompoundTag) fromNative(nativeTag); + removeUnwantedEntityTagsRecursively(tag); + } else { + tag = new net.minecraft.nbt.CompoundTag(); + } + + tag.putString("id", entityId); + + Entity createdEntity = EntityType.loadEntityRecursive(tag, craftWorld.getHandle(), EntitySpawnReason.COMMAND, (loadedEntity) -> { + loadedEntity.absSnapTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); + return loadedEntity; + }); + + if (createdEntity != null) { + worldServer.addFreshEntityWithPassengers(createdEntity, SpawnReason.CUSTOM); + return createdEntity.getBukkitEntity(); + } else { + return null; + } + } + + // This removes all unwanted tags from the main entity and all its passengers + private void removeUnwantedEntityTagsRecursively(net.minecraft.nbt.CompoundTag tag) { + for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) { + tag.remove(name); + } + + // Adapted from net.minecraft.world.entity.EntityType#loadEntityRecursive + tag.getList("Passengers").ifPresent(nbttaglist -> { + for (int i = 0; i < nbttaglist.size(); ++i) { + removeUnwantedEntityTagsRecursively(nbttaglist.getCompoundOrEmpty(i)); + } + }); + } + + @Override + public Component getRichBlockName(BlockType blockType) { + return TranslatableComponent.of(getBlockFromType(blockType).getDescriptionId()); + } + + @Override + public Component getRichItemName(ItemType itemType) { + return TranslatableComponent.of(getItemFromType(itemType).getDescriptionId()); + } + + @Override + public Component getRichItemName(BaseItemStack itemStack) { + return GsonComponentSerializer.INSTANCE.deserialize( + ComponentConverter.Serializer.toJson( + CraftItemStack.asNMSCopy(BukkitAdapter.adapt(itemStack)).getItemName(), + ((CraftServer) Bukkit.getServer()).getServer().registryAccess() + ) + ); + } + + private static final LoadingCache, Property> PROPERTY_CACHE = + CacheBuilder.newBuilder().build(CacheLoader.from(PaperweightFaweAdapter::adaptProperty)); + + @SuppressWarnings({ "rawtypes" }) + @Override + public Map> getProperties(BlockType blockType) { + Map> properties = new TreeMap<>(); + Block block = getBlockFromType(blockType); + StateDefinition blockStateList = + block.getStateDefinition(); + for (net.minecraft.world.level.block.state.properties.Property state : blockStateList.getProperties()) { + Property property = PROPERTY_CACHE.getUnchecked(state); + properties.put(property.getName(), property); + } + return properties; + } + + @Override + public void sendFakeNBT(Player player, BlockVector3 pos, LinCompoundTag nbtData) { + var structureBlock = new StructureBlockEntity( + new BlockPos(pos.x(), pos.y(), pos.z()), + Blocks.STRUCTURE_BLOCK.defaultBlockState() + ); + structureBlock.setLevel(((CraftPlayer) player).getHandle().level()); + ((CraftPlayer) player).getHandle().connection.send(ClientboundBlockEntityDataPacket.create( + structureBlock, + (blockEntity, registryAccess) -> (net.minecraft.nbt.CompoundTag) fromNative(nbtData) + )); + } + + @Override + public void sendFakeOP(Player player) { + ((CraftPlayer) player).getHandle().connection.send(new ClientboundEntityEventPacket( + ((CraftPlayer) player).getHandle(), (byte) 28 + )); + } + + /** + * For serializing and deserializing components. + */ + private static final Codec COMPONENTS_CODEC = DataComponentPatch.CODEC.optionalFieldOf( + "components", DataComponentPatch.EMPTY + ).codec(); + + @Override + public org.bukkit.inventory.ItemStack adapt(BaseItemStack baseItemStack) { + var registryAccess = DedicatedServer.getServer().registryAccess(); + ItemStack stack = new ItemStack( + registryAccess.lookupOrThrow(Registries.ITEM).getOrThrow(ResourceKey.create( + Registries.ITEM, + Identifier.tryParse(baseItemStack.getType().id()) + )), + baseItemStack.getAmount() + ); + LinCompoundTag nbt = baseItemStack.getNbt(); + if (nbt != null) { + DataComponentPatch componentPatch = COMPONENTS_CODEC.parse( + registryAccess.createSerializationContext(NbtOps.INSTANCE), + fromNative(nbt) + ).getOrThrow(); + stack.applyComponents(componentPatch); + } + return CraftItemStack.asCraftMirror(stack); + } + + @Override + public BaseItemStack adapt(org.bukkit.inventory.ItemStack itemStack) { + var registryAccess = DedicatedServer.getServer().registryAccess(); + final ItemStack nmsStack = CraftItemStack.asNMSCopy(itemStack); + CompoundTag tag = (CompoundTag) COMPONENTS_CODEC.encodeStart( + registryAccess.createSerializationContext(NbtOps.INSTANCE), + nmsStack.getComponentsPatch() + ).getOrThrow(); + return new BaseItemStack(BukkitAdapter.asItemType(itemStack.getType()), + LazyReference.from(() -> (LinCompoundTag) toNativeLin(tag)), itemStack.getAmount()); + } + + private final LoadingCache fakePlayers + = CacheBuilder.newBuilder().weakKeys().softValues().build(CacheLoader.from(PaperweightFakePlayer::new)); + + @Override + public boolean simulateItemUse(World world, BlockVector3 position, BaseItem item, Direction face) { + CraftWorld craftWorld = (CraftWorld) world; + ServerLevel worldServer = craftWorld.getHandle(); + ItemStack stack = CraftItemStack.asNMSCopy(adapt( + item instanceof BaseItemStack baseItemStack + ? baseItemStack + : new BaseItemStack(item.getType(), item.getNbtReference(), 1) + )); + + PaperweightFakePlayer fakePlayer; + try { + fakePlayer = fakePlayers.get(worldServer); + } catch (ExecutionException ignored) { + return false; + } + fakePlayer.setItemInHand(InteractionHand.MAIN_HAND, stack); + fakePlayer.absSnapTo(position.x(), position.y(), position.z(), + (float) face.toVector().toYaw(), (float) face.toVector().toPitch()); + + final BlockPos blockPos = new BlockPos(position.x(), position.y(), position.z()); + final Vec3 blockVec = Vec3.atLowerCornerOf(blockPos); + final net.minecraft.core.Direction enumFacing = adapt(face); + BlockHitResult rayTrace = new BlockHitResult(blockVec, enumFacing, blockPos, false); + UseOnContext context = new UseOnContext(fakePlayer, InteractionHand.MAIN_HAND, rayTrace); + InteractionResult result = stack.useOn(context); + if (result != InteractionResult.SUCCESS) { + if (worldServer.getBlockState(blockPos).useItemOn(stack, worldServer, fakePlayer, InteractionHand.MAIN_HAND, rayTrace).consumesAction()) { + result = InteractionResult.SUCCESS; + } else { + result = stack.getItem().use(worldServer, fakePlayer, InteractionHand.MAIN_HAND); + } + } + + return result == InteractionResult.SUCCESS; + } + + @Override + public boolean canPlaceAt(World world, BlockVector3 position, BlockState blockState) { + int internalId = BlockStateIdAccess.getBlockStateId(blockState); + net.minecraft.world.level.block.state.BlockState blockData = Block.stateById(internalId); + return blockData.canSurvive(((CraftWorld) world).getHandle(), new BlockPos(position.x(), position.y(), position.z())); + } + + @Override + public boolean regenerate(World bukkitWorld, Region region, Extent extent, RegenOptions options) { + if (options.getSeed().isPresent()) { + throw new UnsupportedOperationException("26.1+ worldgen does not support overriding the seed for regen"); + } + + try { + doRegen(bukkitWorld, region, extent, options); + } catch (Exception e) { + throw new IllegalStateException("Regen failed.", e); + } + + return true; + } + + private void doRegen(World bukkitWorld, Region region, Extent extent, RegenOptions options) throws Exception { + if (!PaperLib.isPaper()) { + throw new UnsupportedOperationException("Regen requires Paper"); + } + + Environment env = bukkitWorld.getEnvironment(); + ChunkGenerator gen = bukkitWorld.getGenerator(); + + Path tempDir = Files.createTempDirectory("WorldEditWorldGen"); + LevelStorageSource levelStorage = LevelStorageSource.createDefault(tempDir); + ResourceKey worldDimKey = getWorldDimKey(env); + try (LevelStorageSource.LevelStorageAccess session = levelStorage.createAccess("worldeditregentempworld")) { + ServerLevel originalWorld = ((CraftWorld) bukkitWorld).getHandle(); + + PaperWorldLoader.LoadedWorldData loadedWorldData = new PaperWorldLoader.LoadedWorldData( + "worldeditregentempworld", + UUID.randomUUID(), + new PaperWorldPDC((CraftPersistentDataContainer) bukkitWorld.getPersistentDataContainer()), + originalWorld.serverLevelData + ); + + ServerLevel freshWorld = new ServerLevel( + originalWorld.getServer(), + originalWorld.getServer().executor, + session, + originalWorld.worldGenSettings, + originalWorld.dimension(), + new LevelStem( + originalWorld.dimensionTypeRegistration(), + originalWorld.getChunkSource().getGenerator() + ), + originalWorld.isDebug(), + originalWorld.getSeed(), + List.of(), + false, + worldDimKey, + env, + gen, + bukkitWorld.getBiomeProvider(), + originalWorld.getDataStorage(), + loadedWorldData + ); + try { + regenForWorld(region, extent, freshWorld, options); + } finally { + freshWorld.getChunkSource().close(false); + } + } finally { + try { + @SuppressWarnings("unchecked") + Map map = (Map) serverWorldsField.get(Bukkit.getServer()); + map.remove("worldeditregentempworld"); + } catch (IllegalAccessException ignored) { + // It's fine if we couldn't remove it + } + SafeFiles.tryHardToDeleteDir(tempDir); + } + } + + private BiomeType adapt(ServerLevel serverWorld, Biome origBiome) { + Identifier key = serverWorld.registryAccess().lookupOrThrow(Registries.BIOME).getKey(origBiome); + if (key == null) { + return null; + } + return BiomeTypes.get(key.toString()); + } + + @SuppressWarnings("unchecked") + private void regenForWorld(Region region, Extent extent, ServerLevel serverWorld, RegenOptions options) throws WorldEditException { + List> chunkLoadings = submitChunkLoadTasks(region, serverWorld); + BlockableEventLoop executor; + try { + executor = (BlockableEventLoop) chunkProviderExecutorField.get(serverWorld.getChunkSource()); + } catch (IllegalAccessException e) { + throw new IllegalStateException("Couldn't get executor for chunk loading.", e); + } + executor.managedBlock(() -> { + // bail out early if a future fails + if (chunkLoadings.stream().anyMatch(ftr -> + ftr.isDone() && Futures.getUnchecked(ftr) == null + )) { + return false; + } + return chunkLoadings.stream().allMatch(CompletableFuture::isDone); + }); + Map chunks = new HashMap<>(); + for (CompletableFuture future : chunkLoadings) { + @Nullable + ChunkAccess chunk = future.getNow(null); + checkState(chunk != null, "Failed to generate a chunk, regen failed."); + chunks.put(chunk.getPos(), chunk); + } + + for (BlockVector3 vec : region) { + BlockPos pos = new BlockPos(vec.x(), vec.y(), vec.z()); + ChunkAccess chunk = chunks.get(ChunkPos.containing(pos)); + final net.minecraft.world.level.block.state.BlockState blockData = chunk.getBlockState(pos); + int internalId = Block.getId(blockData); + BlockStateHolder state = BlockStateIdAccess.getBlockStateById(internalId); + Objects.requireNonNull(state); + BlockEntity blockEntity = chunk.getBlockEntity(pos); + if (blockEntity != null) { + net.minecraft.nbt.CompoundTag tag = PaperweightLoggingProblemReporter.with( + () -> "serializing block entity at " + pos, + reporter -> { + var tagValueOutput = TagValueOutput.createWithContext(reporter, serverWorld.registryAccess()); + blockEntity.saveWithId(tagValueOutput); + return tagValueOutput.buildResult(); + } + ); + state = state.toBaseBlock(LazyReference.from(() -> (LinCompoundTag) toNativeLin(tag))); + } + extent.setBlock(vec, state.toBaseBlock()); + if (options.shouldRegenBiomes()) { + Biome origBiome = chunk.getNoiseBiome(vec.x(), vec.y(), vec.z()).value(); + BiomeType adaptedBiome = adapt(serverWorld, origBiome); + if (adaptedBiome != null) { + extent.setBiome(vec, adaptedBiome); + } + } + } + } + + @SuppressWarnings("unchecked") + private List> submitChunkLoadTasks(Region region, ServerLevel serverWorld) { + ServerChunkCache chunkManager = serverWorld.getChunkSource(); + List> chunkLoadings = new ArrayList<>(); + // Pre-gen all the chunks + for (BlockVector2 chunk : region.getChunks()) { + try { + //noinspection unchecked + chunkLoadings.add( + ((CompletableFuture>) + getChunkFutureMethod.invoke(chunkManager, chunk.x(), chunk.z(), ChunkStatus.FEATURES, true)) + .thenApply(either -> either.orElse(null)) + ); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new IllegalStateException("Couldn't load chunk for regen.", e); + } + } + return chunkLoadings; + } + + private ResourceKey getWorldDimKey(Environment env) { + return switch (env) { + case NETHER -> LevelStem.NETHER; + case THE_END -> LevelStem.END; + default -> LevelStem.OVERWORLD; + }; + } + + private static final Set SUPPORTED_SIDE_EFFECTS = Sets.immutableEnumSet( + SideEffect.NEIGHBORS, + SideEffect.LIGHTING, + SideEffect.VALIDATION, + SideEffect.ENTITY_AI, + SideEffect.EVENTS, + SideEffect.UPDATE + ); + + @Override + public Set getSupportedSideEffects() { + return SUPPORTED_SIDE_EFFECTS; + } + + @Override + public boolean clearContainerBlockContents(World world, BlockVector3 pt) { + ServerLevel originalWorld = ((CraftWorld) world).getHandle(); + + BlockEntity entity = originalWorld.getBlockEntity(new BlockPos(pt.x(), pt.y(), pt.z())); + if (entity instanceof Clearable clearable) { + clearable.clearContent(); + return true; + } + return false; + } + + @Override + public void initializeRegistries() { + DedicatedServer server = ((CraftServer) Bukkit.getServer()).getServer(); + // Biomes + for (Identifier name : server.registryAccess().lookupOrThrow(Registries.BIOME).keySet()) { + if (BiomeType.REGISTRY.get(name.toString()) == null) { + BiomeType.REGISTRY.register(name.toString(), new BiomeType(name.toString())); + } + } + + // Features + for (Identifier name: server.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).keySet()) { + if (ConfiguredFeatureType.REGISTRY.get(name.toString()) == null) { + ConfiguredFeatureType.REGISTRY.register(name.toString(), new ConfiguredFeatureType(name.toString())); + } + } + + // Structures + for (Identifier name : server.registryAccess().lookupOrThrow(Registries.STRUCTURE).keySet()) { + if (StructureType.REGISTRY.get(name.toString()) == null) { + StructureType.REGISTRY.register(name.toString(), new StructureType(name.toString())); + } + } + + // Trees + Registry placedFeatureRegistry = server.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE); + for (Identifier name : placedFeatureRegistry.keySet()) { + // Do some hackery to make sure this is a tree + var underlyingFeature = placedFeatureRegistry.get(name).get().value().feature().value().feature(); + if (underlyingFeature instanceof TreeFeature || underlyingFeature instanceof FallenTreeFeature || underlyingFeature instanceof CoralTreeFeature) { + String key = name.toString(); + if (TreeType.REGISTRY.get(key) == null) { + TreeType.REGISTRY.register(key, new TreeType(key)); + } + } + } + + // BiomeCategories + Registry biomeRegistry = server.registryAccess().lookupOrThrow(Registries.BIOME); + biomeRegistry.getTags().forEach(tag -> { + String key = tag.key().location().toString(); + if (BiomeCategory.REGISTRY.get(key) == null) { + BiomeCategory.REGISTRY.register(key, new BiomeCategory( + key, + () -> biomeRegistry.get(tag.key()) + .stream() + .flatMap(HolderSet.Named::stream) + .map(Holder::value) + .map(this::adapt) + .collect(Collectors.toSet())) + ); + } + }); + } + + @Override + public boolean generateTree(TreeType treeType, World world, EditSession session, BlockVector3 pt) throws MaxChangedBlocksException { + ServerLevel originalWorld = ((CraftWorld) world).getHandle(); + PlacedFeature feature = originalWorld.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE).getValue(Identifier.tryParse(treeType.id())); + ServerChunkCache chunkManager = originalWorld.getChunkSource(); + try (PaperweightServerLevelDelegateProxy.LevelAndProxy proxyLevel = + PaperweightServerLevelDelegateProxy.newInstance(session, originalWorld, this)) { + return feature != null && feature.place(proxyLevel.level(), chunkManager.getGenerator(), random, new BlockPos(pt.x(), pt.y(), pt.z())); + } + } + + @Override + public boolean generateFeature(ConfiguredFeatureType type, World world, EditSession session, BlockVector3 pt) { + ServerLevel originalWorld = ((CraftWorld) world).getHandle(); + ConfiguredFeature feature = originalWorld.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).getValue(Identifier.tryParse(type.id())); + ServerChunkCache chunkManager = originalWorld.getChunkSource(); + try (PaperweightServerLevelDelegateProxy.LevelAndProxy proxyLevel = + PaperweightServerLevelDelegateProxy.newInstance(session, originalWorld, this)) { + return feature != null && feature.place(proxyLevel.level(), chunkManager.getGenerator(), random, new BlockPos(pt.x(), pt.y(), pt.z())); + } catch (MaxChangedBlocksException e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean generateStructure(StructureType type, World world, EditSession session, BlockVector3 pt) { + ServerLevel originalWorld = ((CraftWorld) world).getHandle(); + Registry structureRegistry = originalWorld.registryAccess().lookupOrThrow(Registries.STRUCTURE); + Structure structure = structureRegistry.getValue(Identifier.tryParse(type.id())); + if (structure == null) { + return false; + } + + ServerChunkCache chunkManager = originalWorld.getChunkSource(); + try (PaperweightServerLevelDelegateProxy.LevelAndProxy proxyLevel = + PaperweightServerLevelDelegateProxy.newInstance(session, originalWorld, this)) { + ChunkPos chunkPos = ChunkPos.containing(new BlockPos(pt.x(), pt.y(), pt.z())); + StructureStart structureStart = structure.generate( + structureRegistry.wrapAsHolder(structure), originalWorld.dimension(), originalWorld.registryAccess(), + chunkManager.getGenerator(), chunkManager.getGenerator().getBiomeSource(), chunkManager.randomState(), + originalWorld.getStructureManager(), originalWorld.getSeed(), chunkPos, 0, + proxyLevel.level(), biome -> true + ); + + if (!structureStart.isValid()) { + return false; + } else { + BoundingBox boundingBox = structureStart.getBoundingBox(); + ChunkPos min = new ChunkPos(SectionPos.blockToSectionCoord(boundingBox.minX()), SectionPos.blockToSectionCoord(boundingBox.minZ())); + ChunkPos max = new ChunkPos(SectionPos.blockToSectionCoord(boundingBox.maxX()), SectionPos.blockToSectionCoord(boundingBox.maxZ())); + ChunkPos.rangeClosed(min, max).forEach((chunkPosx) -> + structureStart.placeInChunk( + proxyLevel.level(), originalWorld.structureManager(), chunkManager.getGenerator(), + originalWorld.getRandom(), + new BoundingBox( + chunkPosx.getMinBlockX(), originalWorld.getMinY(), chunkPosx.getMinBlockZ(), + chunkPosx.getMaxBlockX(), originalWorld.getMaxY(), chunkPosx.getMaxBlockZ() + ), chunkPosx + ) + ); + return true; + } + } catch (MaxChangedBlocksException e) { + throw new RuntimeException(e); + } + } + + @Override + public void sendBiomeUpdates(World world, Iterable chunks) { + ServerLevel originalWorld = ((CraftWorld) world).getHandle(); + + List nativeChunks = chunks instanceof Collection chunkCollection ? Lists.newArrayListWithCapacity(chunkCollection.size()) : Lists.newArrayList(); + for (BlockVector2 chunk : chunks) { + nativeChunks.add(originalWorld.getChunk(chunk.x(), chunk.z(), ChunkStatus.BIOMES, false)); + } + originalWorld.getChunkSource().chunkMap.resendBiomesForChunks(nativeChunks); + } + + // ------------------------------------------------------------------------ + // Code that is less likely to break + // ------------------------------------------------------------------------ + + /** + * Converts from a non-native NMS NBT structure to a native WorldEdit NBT + * structure. + * + * @param foreign non-native NMS NBT structure + * @return native WorldEdit NBT structure + */ + public LinTag toNativeLin(net.minecraft.nbt.Tag foreign) { + if (foreign == null) { + return null; + } + if (foreign instanceof net.minecraft.nbt.CompoundTag compoundTag) { + LinCompoundTag.Builder builder = LinCompoundTag.builder(); + for (var entry : compoundTag.keySet()) { + builder.put(entry, toNativeLin(compoundTag.get(entry))); + } + return builder.build(); + } else if (foreign instanceof net.minecraft.nbt.ByteTag byteTag) { + return LinByteTag.of(byteTag.byteValue()); + } else if (foreign instanceof net.minecraft.nbt.ByteArrayTag byteArrayTag) { + return LinByteArrayTag.of(byteArrayTag.getAsByteArray()); + } else if (foreign instanceof net.minecraft.nbt.DoubleTag doubleTag) { + return LinDoubleTag.of(doubleTag.doubleValue()); + } else if (foreign instanceof net.minecraft.nbt.FloatTag floatTag) { + return LinFloatTag.of(floatTag.floatValue()); + } else if (foreign instanceof net.minecraft.nbt.IntTag intTag) { + return LinIntTag.of(intTag.intValue()); + } else if (foreign instanceof net.minecraft.nbt.IntArrayTag intArrayTag) { + return LinIntArrayTag.of(intArrayTag.getAsIntArray()); + } else if (foreign instanceof net.minecraft.nbt.LongArrayTag longArrayTag) { + return LinLongArrayTag.of(longArrayTag.getAsLongArray()); + } else if (foreign instanceof net.minecraft.nbt.ListTag listTag) { + try { + return toNativeList(listTag); + } catch (Throwable e) { + logger.log(Level.WARNING, "Failed to convert net.minecraft.nbt.ListTag", e); + return LinListTag.empty(LinTagType.endTag()); + } + } else if (foreign instanceof net.minecraft.nbt.LongTag longTag) { + return LinLongTag.of(longTag.longValue()); + } else if (foreign instanceof net.minecraft.nbt.ShortTag shortTag) { + return LinShortTag.of(shortTag.shortValue()); + } else if (foreign instanceof net.minecraft.nbt.StringTag stringTag) { + return LinStringTag.of(stringTag.value()); + } else if (foreign instanceof net.minecraft.nbt.EndTag) { + return LinEndTag.instance(); + } else { + throw new IllegalArgumentException("Don't know how to make native " + foreign.getClass().getCanonicalName()); + } + } + + private static byte identifyRawElementType(net.minecraft.nbt.ListTag list) { + byte b = 0; + + for (Tag tag : list) { + byte c = tag.getId(); + if (b == 0) { + b = c; + } else if (b != c) { + return 10; + } + } + + return b; + } + + private static net.minecraft.nbt.CompoundTag wrapTag(net.minecraft.nbt.Tag tag) { + if (tag instanceof net.minecraft.nbt.CompoundTag compoundTag) { + return compoundTag; + } + var compoundTag = new net.minecraft.nbt.CompoundTag(); + compoundTag.put("", tag); + return compoundTag; + } + + /** + * Convert a foreign NBT list tag into a native WorldEdit one. + * + * @param foreign the foreign tag + * @return the converted tag + * @throws SecurityException on error + * @throws IllegalArgumentException on error + */ + private LinListTag toNativeList(net.minecraft.nbt.ListTag foreign) throws SecurityException, IllegalArgumentException { + byte rawType = identifyRawElementType(foreign); + LinListTag.Builder> builder = LinListTag.builder(LinTagType.fromId( + LinTagId.fromId(rawType) + )); + for (net.minecraft.nbt.Tag tag : foreign) { + if (rawType == LinTagId.COMPOUND.id() && !(tag instanceof net.minecraft.nbt.CompoundTag)) { + builder.add(toNativeLin(wrapTag(tag))); + } else { + builder.add(toNativeLin(tag)); + } + } + return builder.build(); + } + + /** + * Converts a WorldEdit-native NBT structure to a NMS structure. + * + * @param foreign structure to convert + * @return non-native structure + */ + Tag fromNative(LinTag foreign) { + if (foreign == null) { + return null; + } + if (foreign instanceof LinCompoundTag compoundTag) { + net.minecraft.nbt.CompoundTag tag = new CompoundTag(); + for (var entry : compoundTag.value().entrySet()) { + tag.put(entry.getKey(), fromNative(entry.getValue())); + } + return tag; + } else if (foreign instanceof LinByteTag byteTag) { + return ByteTag.valueOf(byteTag.valueAsByte()); + } else if (foreign instanceof LinByteArrayTag byteArrayTag) { + return new ByteArrayTag(byteArrayTag.value()); + } else if (foreign instanceof LinDoubleTag doubleTag) { + return DoubleTag.valueOf(doubleTag.valueAsDouble()); + } else if (foreign instanceof LinFloatTag floatTag) { + return FloatTag.valueOf(floatTag.valueAsFloat()); + } else if (foreign instanceof LinIntTag intTag) { + return IntTag.valueOf(intTag.valueAsInt()); + } else if (foreign instanceof LinIntArrayTag intArrayTag) { + return new IntArrayTag(intArrayTag.value()); + } else if (foreign instanceof LinLongArrayTag longArrayTag) { + return new LongArrayTag(longArrayTag.value()); + } else if (foreign instanceof LinListTag listTag) { + net.minecraft.nbt.ListTag tag = new ListTag(); + for (var t : listTag.value()) { + tag.addAndUnwrap(fromNative(t)); + } + return tag; + } else if (foreign instanceof LinLongTag longTag) { + return LongTag.valueOf(longTag.valueAsLong()); + } else if (foreign instanceof LinShortTag shortTag) { + return ShortTag.valueOf(shortTag.valueAsShort()); + } else if (foreign instanceof LinStringTag stringTag) { + return StringTag.valueOf(stringTag.value()); + } else if (foreign instanceof LinEndTag) { + return EndTag.INSTANCE; + } else { + throw new IllegalArgumentException("Don't know how to make NMS " + foreign.getClass().getCanonicalName()); + } + } + + @Override + public boolean supportsWatchdog() { + return watchdog != null; + } + + @Override + public void tickWatchdog() { + watchdog.tick(); + } + + private class SpigotWatchdog implements Watchdog { + private final Field instanceField; + private final Field lastTickField; + + SpigotWatchdog() throws NoSuchFieldException { + Field instanceField = WatchdogThread.class.getDeclaredField("instance"); + instanceField.setAccessible(true); + this.instanceField = instanceField; + + Field lastTickField = WatchdogThread.class.getDeclaredField("lastTick"); + lastTickField.setAccessible(true); + this.lastTickField = lastTickField; + } + + @Override + public void tick() { + try { + WatchdogThread instance = (WatchdogThread) this.instanceField.get(null); + if ((long) lastTickField.get(instance) != 0) { + WatchdogThread.tick(); + } + } catch (IllegalAccessException e) { + logger.log(Level.WARNING, "Failed to tick watchdog", e); + } + } + } + + private static class MojangWatchdog implements Watchdog { + private final DedicatedServer server; + private final Field tickField; + + MojangWatchdog(DedicatedServer server) throws NoSuchFieldException { + this.server = server; + Field tickField = MinecraftServer.class.getDeclaredField(StaticRefraction.NEXT_TICK_TIME); + if (tickField.getType() != long.class) { + throw new IllegalStateException("nextTickTime is not a long field, mapping is likely incorrect"); + } + tickField.setAccessible(true); + this.tickField = tickField; + } + + @Override + public void tick() { + try { + tickField.set(server, Util.getMillis()); + } catch (IllegalAccessException ignored) { + // It's fine if we couldn't set it + } + } + } +} diff --git a/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v26_1/PaperweightBlockMaterial.java b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v26_1/PaperweightBlockMaterial.java new file mode 100644 index 0000000000..179d621045 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v26_1/PaperweightBlockMaterial.java @@ -0,0 +1,142 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.bukkit.adapter.impl.v26_1; + +import com.fastasyncworldedit.bukkit.adapter.BukkitBlockMaterial; +import com.fastasyncworldedit.core.nbt.FaweCompoundTag; +import com.sk89q.worldedit.bukkit.adapter.impl.fawe.v26_1.PaperweightGetBlocks; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.EmptyBlockGetter; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.Fluids; +import net.minecraft.world.level.material.PushReaction; + +public class PaperweightBlockMaterial extends BukkitBlockMaterial { + + public PaperweightBlockMaterial(Block block) { + this(block, block.defaultBlockState()); + } + + public PaperweightBlockMaterial(Block block, BlockState blockState) { + super(block, blockState, blockState.asBlockData()); + } + + @Override + protected FaweCompoundTag tileForBlock(final Block block) { + BlockEntity tileEntity = !(block instanceof EntityBlock eb) ? null : eb.newBlockEntity(BlockPos.ZERO, this.blockState); + return tileEntity == null ? null : PaperweightGetBlocks.NMS_TO_TILE.apply(tileEntity); + } + + @Override + public boolean isAir() { + return this.blockState.isAir(); + } + + @Override + public boolean isFullCube() { + return Block.isShapeFullBlock(this.blockState.getShape(EmptyBlockGetter.INSTANCE, BlockPos.ZERO)); + } + + @Override + public boolean isOpaque() { + return this.blockState.canOcclude(); + } + + @Override + public boolean isPowerSource() { + return this.blockState.isSignalSource(); + } + + @Override + public boolean isLiquid() { + return !this.blockState.getFluidState().is(Fluids.EMPTY); + } + + @Override + public boolean isSolid() { + return this.blockState.isSolidRender(); + } + + @Override + public float getHardness() { + return this.blockState.destroySpeed; + } + + @Override + public float getResistance() { + return this.block.getExplosionResistance(); + } + + @Override + public float getSlipperiness() { + return this.block.getFriction(); + } + + @Override + public int getLightValue() { + return this.blockState.getLightEmission(); + } + + @Override + public int getLightOpacity() { + return this.blockState.getLightDampening(); + } + + @Override + public boolean isFragileWhenPushed() { + return this.blockState.getPistonPushReaction() == PushReaction.DESTROY; + } + + @Override + public boolean isUnpushable() { + return this.blockState.getPistonPushReaction() == PushReaction.BLOCK; + } + + @Override + public boolean isTicksRandomly() { + return this.blockState.isRandomlyTicking(); + } + + @SuppressWarnings("deprecation") + @Override + public boolean isMovementBlocker() { + return this.blockState.blocksMotion(); + } + + @Override + public boolean isReplacedDuringPlacement() { + return this.blockState.canBeReplaced(); + } + + @Override + public boolean isTranslucent() { + return !this.blockState.canOcclude(); + } + + @Override + public int getMapColor() { + // rgb field + return this.block.defaultMapColor().col; + } + +} diff --git a/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v26_1/PaperweightDataConverters.java b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v26_1/PaperweightDataConverters.java new file mode 100644 index 0000000000..c1f3f0aea8 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v26_1/PaperweightDataConverters.java @@ -0,0 +1,2766 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.bukkit.adapter.impl.v26_1; + +import com.google.common.base.Strings; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.mojang.datafixers.DSL.TypeReference; +import com.mojang.datafixers.DataFixer; +import com.mojang.datafixers.schemas.Schema; +import com.mojang.serialization.Dynamic; +import net.minecraft.core.Direction; +import net.minecraft.core.UUIDUtil; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.FloatTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.StringTag; +import net.minecraft.nbt.Tag; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.resources.Identifier; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.GsonHelper; +import net.minecraft.util.datafix.DataFixers; +import net.minecraft.util.datafix.fixes.References; +import net.minecraft.world.item.DyeColor; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.enginehub.linbus.tree.LinCompoundTag; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.Random; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; +import javax.annotation.Nullable; + +/** + * Handles converting all Pre 1.13.2 data using the Legacy DataFix System (ported to 1.13.2) + * + *

+ * We register a DFU Fixer per Legacy Data Version and apply the fixes using legacy strategy + * which is safer, faster and cleaner code. + *

+ * + *

+ * The pre DFU code did not fail when the Source version was unknown. + *

+ * + *

+ * This class also provides util methods for converting compounds to wrap the update call to + * receive the source version in the compound + *

+ */ +@SuppressWarnings({ + "UnnecessarilyQualifiedStaticUsage", + "StringSplitter", + "ImmutableEnumChecker", + "MissingOverride", + "StaticAssignmentInConstructor", + "EffectivelyPrivate", + "FallThrough", + "MutablePublicArray", + "unused", + "unchecked", + "rawtypes" +}) +class PaperweightDataConverters implements com.sk89q.worldedit.world.DataFixer { + + @SuppressWarnings("unchecked") + @Override + public T fixUp(FixType type, T original, int srcVer) { + if (type == FixTypes.CHUNK) { + return (T) fixChunk((LinCompoundTag) original, srcVer); + } else if (type == FixTypes.BLOCK_ENTITY) { + return (T) fixBlockEntity((LinCompoundTag) original, srcVer); + } else if (type == FixTypes.ENTITY) { + return (T) fixEntity((LinCompoundTag) original, srcVer); + } else if (type == FixTypes.BLOCK_STATE) { + return (T) fixBlockState((String) original, srcVer); + } else if (type == FixTypes.ITEM_TYPE) { + return (T) fixItemType((String) original, srcVer); + } else if (type == FixTypes.BIOME) { + return (T) fixBiome((String) original, srcVer); + } + return original; + } + + private LinCompoundTag fixChunk(LinCompoundTag originalChunk, int srcVer) { + CompoundTag tag = (CompoundTag) adapter.fromNative(originalChunk); + CompoundTag fixed = convert(LegacyType.CHUNK, tag, srcVer); + return (LinCompoundTag) adapter.toNativeLin(fixed); + } + + private LinCompoundTag fixBlockEntity(LinCompoundTag origTileEnt, int srcVer) { + CompoundTag tag = (CompoundTag) adapter.fromNative(origTileEnt); + CompoundTag fixed = convert(LegacyType.BLOCK_ENTITY, tag, srcVer); + return (LinCompoundTag) adapter.toNativeLin(fixed); + } + + private LinCompoundTag fixEntity(LinCompoundTag origEnt, int srcVer) { + CompoundTag tag = (CompoundTag) adapter.fromNative(origEnt); + CompoundTag fixed = convert(LegacyType.ENTITY, tag, srcVer); + return (LinCompoundTag) adapter.toNativeLin(fixed); + } + + private String fixBlockState(String blockState, int srcVer) { + CompoundTag stateNBT = stateToNBT(blockState); + Dynamic dynamic = new Dynamic<>(OPS_NBT, stateNBT); + CompoundTag fixed = (CompoundTag) INSTANCE.fixer.update(References.BLOCK_STATE, dynamic, srcVer, DATA_VERSION).getValue(); + return nbtToState(fixed); + } + + private String nbtToState(net.minecraft.nbt.CompoundTag tagCompound) { + StringBuilder sb = new StringBuilder(); + sb.append(tagCompound.getString("Name").get()); + tagCompound.getCompound("Properties").ifPresent(props -> { + sb.append('['); + sb.append(props.keySet().stream().map(k -> k + "=" + props.getString(k).get().replace("\"", "")).collect(Collectors.joining(","))); + sb.append(']'); + }); + return sb.toString(); + } + + private static CompoundTag stateToNBT(String blockState) { + int propIdx = blockState.indexOf('['); + CompoundTag tag = new CompoundTag(); + if (propIdx < 0) { + tag.putString("Name", blockState); + } else { + tag.putString("Name", blockState.substring(0, propIdx)); + CompoundTag propTag = new CompoundTag(); + String props = blockState.substring(propIdx + 1, blockState.length() - 1); + String[] propArr = props.split(","); + for (String pair : propArr) { + final String[] split = pair.split("="); + propTag.putString(split[0], split[1]); + } + tag.put("Properties", propTag); + } + return tag; + } + + private String fixBiome(String key, int srcVer) { + return fixName(key, srcVer, References.BIOME); + } + + private String fixItemType(String key, int srcVer) { + return fixName(key, srcVer, References.ITEM_NAME); + } + + private static String fixName(String key, int srcVer, TypeReference type) { + return INSTANCE.fixer.update(type, new Dynamic<>(OPS_NBT, StringTag.valueOf(key)), srcVer, DATA_VERSION) + .asString().result().orElse(key); + } + + private final PaperweightAdapter adapter; + + private static final NbtOps OPS_NBT = NbtOps.INSTANCE; + private static final int LEGACY_VERSION = 1343; + private static int DATA_VERSION; + static PaperweightDataConverters INSTANCE; + + private final Map> converters = new EnumMap<>(LegacyType.class); + private final Map> inspectors = new EnumMap<>(LegacyType.class); + + // Set on build + private DataFixer fixer; + private static final Map DFU_TO_LEGACY = new HashMap<>(); + + public enum LegacyType { + LEVEL(References.LEVEL), + PLAYER(References.PLAYER), + CHUNK(References.CHUNK), + BLOCK_ENTITY(References.BLOCK_ENTITY), + ENTITY(References.ENTITY), + ITEM_INSTANCE(References.ITEM_STACK), + OPTIONS(References.OPTIONS), + STRUCTURE(References.STRUCTURE); + + private final TypeReference type; + + LegacyType(TypeReference type) { + this.type = type; + DFU_TO_LEGACY.put(type.typeName(), this); + } + + public TypeReference getDFUType() { + return type; + } + } + + PaperweightDataConverters(int dataVersion, PaperweightAdapter adapter) { + DATA_VERSION = dataVersion; + INSTANCE = this; + this.adapter = adapter; + registerConverters(); + registerInspectors(); + this.fixer = new WrappedDataFixer(DataFixers.getDataFixer()); + } + + @SuppressWarnings("unchecked") + private class WrappedDataFixer implements DataFixer { + private final DataFixer realFixer; + + WrappedDataFixer(DataFixer realFixer) { + this.realFixer = realFixer; + } + + @Override + public Dynamic update(TypeReference type, Dynamic dynamic, int sourceVer, int targetVer) { + LegacyType legacyType = DFU_TO_LEGACY.get(type.typeName()); + if (sourceVer < LEGACY_VERSION && legacyType != null) { + CompoundTag cmp = (CompoundTag) dynamic.getValue(); + int desiredVersion = Math.min(targetVer, LEGACY_VERSION); + + cmp = convert(legacyType, cmp, sourceVer, desiredVersion); + sourceVer = desiredVersion; + dynamic = new Dynamic(OPS_NBT, cmp); + } + return realFixer.update(type, dynamic, sourceVer, targetVer); + } + + private CompoundTag convert(LegacyType type, CompoundTag cmp, int sourceVer, int desiredVersion) { + List converters = PaperweightDataConverters.this.converters.get(type); + if (converters != null && !converters.isEmpty()) { + for (DataConverter converter : converters) { + int dataVersion = converter.getDataVersion(); + if (dataVersion > sourceVer && dataVersion <= desiredVersion) { + cmp = converter.convert(cmp); + } + } + } + + List inspectors = PaperweightDataConverters.this.inspectors.get(type); + if (inspectors != null && !inspectors.isEmpty()) { + for (DataInspector inspector : inspectors) { + cmp = inspector.inspect(cmp, sourceVer, desiredVersion); + } + } + + return cmp; + } + + @Override + public Schema getSchema(int i) { + return realFixer.getSchema(i); + } + } + + public static CompoundTag convert(LegacyType type, CompoundTag cmp) { + return convert(type.getDFUType(), cmp); + } + + public static CompoundTag convert(LegacyType type, CompoundTag cmp, int sourceVer) { + return convert(type.getDFUType(), cmp, sourceVer); + } + + public static CompoundTag convert(LegacyType type, CompoundTag cmp, int sourceVer, int targetVer) { + return convert(type.getDFUType(), cmp, sourceVer, targetVer); + } + + public static CompoundTag convert(TypeReference type, CompoundTag cmp) { + int i = cmp.getIntOr("DataVersion", -1); + return convert(type, cmp, i); + } + + public static CompoundTag convert(TypeReference type, CompoundTag cmp, int sourceVer) { + return convert(type, cmp, sourceVer, DATA_VERSION); + } + + public static CompoundTag convert(TypeReference type, CompoundTag cmp, int sourceVer, int targetVer) { + if (sourceVer >= targetVer) { + return cmp; + } + return (CompoundTag) INSTANCE.fixer.update(type, new Dynamic<>(OPS_NBT, cmp), sourceVer, targetVer).getValue(); + } + + + public interface DataInspector { + CompoundTag inspect(CompoundTag cmp, int sourceVer, int targetVer); + } + + public interface DataConverter { + + int getDataVersion(); + + CompoundTag convert(CompoundTag cmp); + } + + + private void registerInspector(LegacyType type, DataInspector inspector) { + this.inspectors.computeIfAbsent(type, k -> new ArrayList<>()).add(inspector); + } + + private void registerConverter(LegacyType type, DataConverter converter) { + int version = converter.getDataVersion(); + + List list = this.converters.computeIfAbsent(type, k -> new ArrayList<>()); + if (!list.isEmpty() && list.get(list.size() - 1).getDataVersion() > version) { + for (int j = 0; j < list.size(); ++j) { + if (list.get(j).getDataVersion() > version) { + list.add(j, converter); + break; + } + } + } else { + list.add(converter); + } + } + + private void registerInspectors() { + registerEntityItemList("EntityHorseDonkey", "SaddleItem", "Items"); + registerEntityItemList("EntityHorseMule", "Items"); + registerEntityItemList("EntityMinecartChest", "Items"); + registerEntityItemList("EntityMinecartHopper", "Items"); + registerEntityItemList("EntityVillager", "Inventory"); + registerEntityItemListEquipment("EntityArmorStand"); + registerEntityItemListEquipment("EntityBat"); + registerEntityItemListEquipment("EntityBlaze"); + registerEntityItemListEquipment("EntityCaveSpider"); + registerEntityItemListEquipment("EntityChicken"); + registerEntityItemListEquipment("EntityCow"); + registerEntityItemListEquipment("EntityCreeper"); + registerEntityItemListEquipment("EntityEnderDragon"); + registerEntityItemListEquipment("EntityEnderman"); + registerEntityItemListEquipment("EntityEndermite"); + registerEntityItemListEquipment("EntityEvoker"); + registerEntityItemListEquipment("EntityGhast"); + registerEntityItemListEquipment("EntityGiantZombie"); + registerEntityItemListEquipment("EntityGuardian"); + registerEntityItemListEquipment("EntityGuardianElder"); + registerEntityItemListEquipment("EntityHorse"); + registerEntityItemListEquipment("EntityHorseDonkey"); + registerEntityItemListEquipment("EntityHorseMule"); + registerEntityItemListEquipment("EntityHorseSkeleton"); + registerEntityItemListEquipment("EntityHorseZombie"); + registerEntityItemListEquipment("EntityIronGolem"); + registerEntityItemListEquipment("EntityMagmaCube"); + registerEntityItemListEquipment("EntityMushroomCow"); + registerEntityItemListEquipment("EntityOcelot"); + registerEntityItemListEquipment("EntityPig"); + registerEntityItemListEquipment("EntityPigZombie"); + registerEntityItemListEquipment("EntityRabbit"); + registerEntityItemListEquipment("EntitySheep"); + registerEntityItemListEquipment("EntityShulker"); + registerEntityItemListEquipment("EntitySilverfish"); + registerEntityItemListEquipment("EntitySkeleton"); + registerEntityItemListEquipment("EntitySkeletonStray"); + registerEntityItemListEquipment("EntitySkeletonWither"); + registerEntityItemListEquipment("EntitySlime"); + registerEntityItemListEquipment("EntitySnowman"); + registerEntityItemListEquipment("EntitySpider"); + registerEntityItemListEquipment("EntitySquid"); + registerEntityItemListEquipment("EntityVex"); + registerEntityItemListEquipment("EntityVillager"); + registerEntityItemListEquipment("EntityVindicator"); + registerEntityItemListEquipment("EntityWitch"); + registerEntityItemListEquipment("EntityWither"); + registerEntityItemListEquipment("EntityWolf"); + registerEntityItemListEquipment("EntityZombie"); + registerEntityItemListEquipment("EntityZombieHusk"); + registerEntityItemListEquipment("EntityZombieVillager"); + registerEntityItemSingle("EntityFireworks", "FireworksItem"); + registerEntityItemSingle("EntityHorse", "ArmorItem"); + registerEntityItemSingle("EntityHorse", "SaddleItem"); + registerEntityItemSingle("EntityHorseMule", "SaddleItem"); + registerEntityItemSingle("EntityHorseSkeleton", "SaddleItem"); + registerEntityItemSingle("EntityHorseZombie", "SaddleItem"); + registerEntityItemSingle("EntityItem", "Item"); + registerEntityItemSingle("EntityItemFrame", "Item"); + registerEntityItemSingle("EntityPotion", "Potion"); + + registerInspector(LegacyType.BLOCK_ENTITY, new DataInspectorItem("TileEntityRecordPlayer", "RecordItem")); + registerInspector(LegacyType.BLOCK_ENTITY, new DataInspectorItemList("TileEntityBrewingStand", "Items")); + registerInspector(LegacyType.BLOCK_ENTITY, new DataInspectorItemList("TileEntityChest", "Items")); + registerInspector(LegacyType.BLOCK_ENTITY, new DataInspectorItemList("TileEntityDispenser", "Items")); + registerInspector(LegacyType.BLOCK_ENTITY, new DataInspectorItemList("TileEntityDropper", "Items")); + registerInspector(LegacyType.BLOCK_ENTITY, new DataInspectorItemList("TileEntityFurnace", "Items")); + registerInspector(LegacyType.BLOCK_ENTITY, new DataInspectorItemList("TileEntityHopper", "Items")); + registerInspector(LegacyType.BLOCK_ENTITY, new DataInspectorItemList("TileEntityShulkerBox", "Items")); + registerInspector(LegacyType.BLOCK_ENTITY, new DataInspectorMobSpawnerMobs()); + registerInspector(LegacyType.CHUNK, new DataInspectorChunks()); + registerInspector(LegacyType.ENTITY, new DataInspectorCommandBlock()); + registerInspector(LegacyType.ENTITY, new DataInspectorEntityPassengers()); + registerInspector(LegacyType.ENTITY, new DataInspectorMobSpawnerMinecart()); + registerInspector(LegacyType.ENTITY, new DataInspectorVillagers()); + registerInspector(LegacyType.ITEM_INSTANCE, new DataInspectorBlockEntity()); + registerInspector(LegacyType.ITEM_INSTANCE, new DataInspectorEntity()); + registerInspector(LegacyType.LEVEL, new DataInspectorLevelPlayer()); + registerInspector(LegacyType.PLAYER, new DataInspectorPlayer()); + registerInspector(LegacyType.PLAYER, new DataInspectorPlayerVehicle()); + registerInspector(LegacyType.STRUCTURE, new DataInspectorStructure()); + } + + private void registerConverters() { + registerConverter(LegacyType.ENTITY, new DataConverterEquipment()); + registerConverter(LegacyType.BLOCK_ENTITY, new DataConverterSignText()); + registerConverter(LegacyType.ITEM_INSTANCE, new DataConverterMaterialId()); + registerConverter(LegacyType.ITEM_INSTANCE, new DataConverterPotionId()); + registerConverter(LegacyType.ITEM_INSTANCE, new DataConverterSpawnEgg()); + registerConverter(LegacyType.ENTITY, new DataConverterMinecart()); + registerConverter(LegacyType.BLOCK_ENTITY, new DataConverterMobSpawner()); + registerConverter(LegacyType.ENTITY, new DataConverterUUID()); + registerConverter(LegacyType.ENTITY, new DataConverterHealth()); + registerConverter(LegacyType.ENTITY, new DataConverterSaddle()); + registerConverter(LegacyType.ENTITY, new DataConverterHanging()); + registerConverter(LegacyType.ENTITY, new DataConverterDropChances()); + registerConverter(LegacyType.ENTITY, new DataConverterRiding()); + registerConverter(LegacyType.ENTITY, new DataConverterArmorStand()); + registerConverter(LegacyType.ITEM_INSTANCE, new DataConverterBook()); + registerConverter(LegacyType.ITEM_INSTANCE, new DataConverterCookedFish()); + registerConverter(LegacyType.ENTITY, new DataConverterZombie()); + registerConverter(LegacyType.OPTIONS, new DataConverterVBO()); + registerConverter(LegacyType.ENTITY, new DataConverterGuardian()); + registerConverter(LegacyType.ENTITY, new DataConverterSkeleton()); + registerConverter(LegacyType.ENTITY, new DataConverterZombieType()); + registerConverter(LegacyType.ENTITY, new DataConverterHorse()); + registerConverter(LegacyType.BLOCK_ENTITY, new DataConverterTileEntity()); + registerConverter(LegacyType.ENTITY, new DataConverterEntity()); + registerConverter(LegacyType.ITEM_INSTANCE, new DataConverterBanner()); + registerConverter(LegacyType.ITEM_INSTANCE, new DataConverterPotionWater()); + registerConverter(LegacyType.ENTITY, new DataConverterShulker()); + registerConverter(LegacyType.ITEM_INSTANCE, new DataConverterShulkerBoxItem()); + registerConverter(LegacyType.BLOCK_ENTITY, new DataConverterShulkerBoxBlock()); + registerConverter(LegacyType.OPTIONS, new DataConverterLang()); + registerConverter(LegacyType.ITEM_INSTANCE, new DataConverterTotem()); + registerConverter(LegacyType.CHUNK, new DataConverterBedBlock()); + registerConverter(LegacyType.ITEM_INSTANCE, new DataConverterBedItem()); + } + + private void registerEntityItemList(String type, String... keys) { + registerInspector(LegacyType.ENTITY, new DataInspectorItemList(type, keys)); + } + + private void registerEntityItemSingle(String type, String key) { + registerInspector(LegacyType.ENTITY, new DataInspectorItem(type, key)); + } + + private void registerEntityItemListEquipment(String type) { + registerEntityItemList(type, "ArmorItems", "HandItems"); + } + + private static final Map OLD_ID_TO_KEY_MAP = new HashMap<>(); + + static { + final Map map = OLD_ID_TO_KEY_MAP; + map.put("EntityItem", Identifier.parse("item")); + map.put("EntityExperienceOrb", Identifier.parse("xp_orb")); + map.put("EntityAreaEffectCloud", Identifier.parse("area_effect_cloud")); + map.put("EntityGuardianElder", Identifier.parse("elder_guardian")); + map.put("EntitySkeletonWither", Identifier.parse("wither_skeleton")); + map.put("EntitySkeletonStray", Identifier.parse("stray")); + map.put("EntityEgg", Identifier.parse("egg")); + map.put("EntityLeash", Identifier.parse("leash_knot")); + map.put("EntityPainting", Identifier.parse("painting")); + map.put("EntityTippedArrow", Identifier.parse("arrow")); + map.put("EntitySnowball", Identifier.parse("snowball")); + map.put("EntityLargeFireball", Identifier.parse("fireball")); + map.put("EntitySmallFireball", Identifier.parse("small_fireball")); + map.put("EntityEnderPearl", Identifier.parse("ender_pearl")); + map.put("EntityEnderSignal", Identifier.parse("eye_of_ender_signal")); + map.put("EntityPotion", Identifier.parse("potion")); + map.put("EntityThrownExpBottle", Identifier.parse("xp_bottle")); + map.put("EntityItemFrame", Identifier.parse("item_frame")); + map.put("EntityWitherSkull", Identifier.parse("wither_skull")); + map.put("EntityTNTPrimed", Identifier.parse("tnt")); + map.put("EntityFallingBlock", Identifier.parse("falling_block")); + map.put("EntityFireworks", Identifier.parse("fireworks_rocket")); + map.put("EntityZombieHusk", Identifier.parse("husk")); + map.put("EntitySpectralArrow", Identifier.parse("spectral_arrow")); + map.put("EntityShulkerBullet", Identifier.parse("shulker_bullet")); + map.put("EntityDragonFireball", Identifier.parse("dragon_fireball")); + map.put("EntityZombieVillager", Identifier.parse("zombie_villager")); + map.put("EntityHorseSkeleton", Identifier.parse("skeleton_horse")); + map.put("EntityHorseZombie", Identifier.parse("zombie_horse")); + map.put("EntityArmorStand", Identifier.parse("armor_stand")); + map.put("EntityHorseDonkey", Identifier.parse("donkey")); + map.put("EntityHorseMule", Identifier.parse("mule")); + map.put("EntityEvokerFangs", Identifier.parse("evocation_fangs")); + map.put("EntityEvoker", Identifier.parse("evocation_illager")); + map.put("EntityVex", Identifier.parse("vex")); + map.put("EntityVindicator", Identifier.parse("vindication_illager")); + map.put("EntityIllagerIllusioner", Identifier.parse("illusion_illager")); + map.put("EntityMinecartCommandBlock", Identifier.parse("commandblock_minecart")); + map.put("EntityBoat", Identifier.parse("boat")); + map.put("EntityMinecartRideable", Identifier.parse("minecart")); + map.put("EntityMinecartChest", Identifier.parse("chest_minecart")); + map.put("EntityMinecartFurnace", Identifier.parse("furnace_minecart")); + map.put("EntityMinecartTNT", Identifier.parse("tnt_minecart")); + map.put("EntityMinecartHopper", Identifier.parse("hopper_minecart")); + map.put("EntityMinecartMobSpawner", Identifier.parse("spawner_minecart")); + map.put("EntityCreeper", Identifier.parse("creeper")); + map.put("EntitySkeleton", Identifier.parse("skeleton")); + map.put("EntitySpider", Identifier.parse("spider")); + map.put("EntityGiantZombie", Identifier.parse("giant")); + map.put("EntityZombie", Identifier.parse("zombie")); + map.put("EntitySlime", Identifier.parse("slime")); + map.put("EntityGhast", Identifier.parse("ghast")); + map.put("EntityPigZombie", Identifier.parse("zombie_pigman")); + map.put("EntityEnderman", Identifier.parse("enderman")); + map.put("EntityCaveSpider", Identifier.parse("cave_spider")); + map.put("EntitySilverfish", Identifier.parse("silverfish")); + map.put("EntityBlaze", Identifier.parse("blaze")); + map.put("EntityMagmaCube", Identifier.parse("magma_cube")); + map.put("EntityEnderDragon", Identifier.parse("ender_dragon")); + map.put("EntityWither", Identifier.parse("wither")); + map.put("EntityBat", Identifier.parse("bat")); + map.put("EntityWitch", Identifier.parse("witch")); + map.put("EntityEndermite", Identifier.parse("endermite")); + map.put("EntityGuardian", Identifier.parse("guardian")); + map.put("EntityShulker", Identifier.parse("shulker")); + map.put("EntityPig", Identifier.parse("pig")); + map.put("EntitySheep", Identifier.parse("sheep")); + map.put("EntityCow", Identifier.parse("cow")); + map.put("EntityChicken", Identifier.parse("chicken")); + map.put("EntitySquid", Identifier.parse("squid")); + map.put("EntityWolf", Identifier.parse("wolf")); + map.put("EntityMushroomCow", Identifier.parse("mooshroom")); + map.put("EntitySnowman", Identifier.parse("snowman")); + map.put("EntityOcelot", Identifier.parse("ocelot")); + map.put("EntityIronGolem", Identifier.parse("villager_golem")); + map.put("EntityHorse", Identifier.parse("horse")); + map.put("EntityRabbit", Identifier.parse("rabbit")); + map.put("EntityPolarBear", Identifier.parse("polar_bear")); + map.put("EntityLlama", Identifier.parse("llama")); + map.put("EntityLlamaSpit", Identifier.parse("llama_spit")); + map.put("EntityParrot", Identifier.parse("parrot")); + map.put("EntityVillager", Identifier.parse("villager")); + map.put("EntityEnderCrystal", Identifier.parse("ender_crystal")); + map.put("TileEntityFurnace", Identifier.parse("furnace")); + map.put("TileEntityChest", Identifier.parse("chest")); + map.put("TileEntityEnderChest", Identifier.parse("ender_chest")); + map.put("TileEntityRecordPlayer", Identifier.parse("jukebox")); + map.put("TileEntityDispenser", Identifier.parse("dispenser")); + map.put("TileEntityDropper", Identifier.parse("dropper")); + map.put("TileEntitySign", Identifier.parse("sign")); + map.put("TileEntityMobSpawner", Identifier.parse("mob_spawner")); + map.put("TileEntityNote", Identifier.parse("noteblock")); + map.put("TileEntityPiston", Identifier.parse("piston")); + map.put("TileEntityBrewingStand", Identifier.parse("brewing_stand")); + map.put("TileEntityEnchantTable", Identifier.parse("enchanting_table")); + map.put("TileEntityEnderPortal", Identifier.parse("end_portal")); + map.put("TileEntityBeacon", Identifier.parse("beacon")); + map.put("TileEntitySkull", Identifier.parse("skull")); + map.put("TileEntityLightDetector", Identifier.parse("daylight_detector")); + map.put("TileEntityHopper", Identifier.parse("hopper")); + map.put("TileEntityComparator", Identifier.parse("comparator")); + map.put("TileEntityFlowerPot", Identifier.parse("flower_pot")); + map.put("TileEntityBanner", Identifier.parse("banner")); + map.put("TileEntityStructure", Identifier.parse("structure_block")); + map.put("TileEntityEndGateway", Identifier.parse("end_gateway")); + map.put("TileEntityCommand", Identifier.parse("command_block")); + map.put("TileEntityShulkerBox", Identifier.parse("shulker_box")); + map.put("TileEntityBed", Identifier.parse("bed")); + } + + private static Identifier getKey(String type) { + final Identifier key = OLD_ID_TO_KEY_MAP.get(type); + if (key == null) { + throw new IllegalArgumentException("Unknown mapping for " + type); + } + return key; + } + + private static void convertCompound(LegacyType type, net.minecraft.nbt.CompoundTag cmp, String key, int sourceVer, int targetVer) { + cmp.put(key, convert(type, cmp.getCompoundOrEmpty(key), sourceVer, targetVer)); + } + + private static void convertItem(net.minecraft.nbt.CompoundTag nbttagcompound, String key, int sourceVer, int targetVer) { + if (nbttagcompound.getCompound(key).isPresent()) { + convertCompound(LegacyType.ITEM_INSTANCE, nbttagcompound, key, sourceVer, targetVer); + } + } + + private static void convertItems(net.minecraft.nbt.CompoundTag nbttagcompound, String key, int sourceVer, int targetVer) { + nbttagcompound.getList(key).ifPresent(nbttaglist -> { + for (int j = 0; j < nbttaglist.size(); ++j) { + nbttaglist.set(j, convert(LegacyType.ITEM_INSTANCE, nbttaglist.getCompoundOrEmpty(j), sourceVer, targetVer)); + } + }); + } + + private static class DataConverterEquipment implements DataConverter { + + DataConverterEquipment() { + } + + @Override + public int getDataVersion() { + return 100; + } + + @Override + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + ListTag nbttaglist = cmp.getListOrEmpty("Equipment"); + + if (!nbttaglist.isEmpty() && cmp.getCompound("HandItems").isEmpty()) { + ListTag nbttaglist1 = new ListTag(); + nbttaglist1.add(nbttaglist.get(0)); + nbttaglist1.add(new net.minecraft.nbt.CompoundTag()); + cmp.put("HandItems", nbttaglist1); + } + + if (nbttaglist.size() > 1 && cmp.getCompound("ArmorItem").isEmpty()) { + ListTag nbttaglist1 = new ListTag(); + nbttaglist1.add(nbttaglist.get(1)); + nbttaglist1.add(nbttaglist.get(2)); + nbttaglist1.add(nbttaglist.get(3)); + nbttaglist1.add(nbttaglist.get(4)); + cmp.put("ArmorItems", nbttaglist1); + } + + cmp.remove("Equipment"); + cmp.getList("DropChances").ifPresent(nbttaglist1 -> { + ListTag nbttaglist2; + + if (cmp.getCompound("HandDropChances").isEmpty()) { + nbttaglist2 = new ListTag(); + nbttaglist2.add(FloatTag.valueOf(nbttaglist1.getFloatOr(0, 0F))); + nbttaglist2.add(FloatTag.valueOf(0.0F)); + cmp.put("HandDropChances", nbttaglist2); + } + + if (cmp.getCompound("ArmorDropChances").isEmpty()) { + nbttaglist2 = new ListTag(); + nbttaglist2.add(FloatTag.valueOf(nbttaglist1.getFloatOr(1, 0F))); + nbttaglist2.add(FloatTag.valueOf(nbttaglist1.getFloatOr(2, 0F))); + nbttaglist2.add(FloatTag.valueOf(nbttaglist1.getFloatOr(3, 0F))); + nbttaglist2.add(FloatTag.valueOf(nbttaglist1.getFloatOr(4, 0F))); + cmp.put("ArmorDropChances", nbttaglist2); + } + + cmp.remove("DropChances"); + }); + + return cmp; + } + } + + private static class DataInspectorBlockEntity implements DataInspector { + + private static final Map b = Maps.newHashMap(); + private static final Map c = Maps.newHashMap(); + + DataInspectorBlockEntity() { + } + + @Nullable + private static String convertEntityId(int i, String s) { + String key = Identifier.parse(s).toString(); + if (i < 515 && DataInspectorBlockEntity.b.containsKey(key)) { + return DataInspectorBlockEntity.b.get(key); + } else { + return DataInspectorBlockEntity.c.get(key); + } + } + + @Override + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + Optional nbttagcompound1Optional = cmp.getCompound("tag"); + + if (nbttagcompound1Optional.isPresent()) { + var nbttagcompound1 = nbttagcompound1Optional.get(); + + nbttagcompound1.getCompound("BlockEntityTag").ifPresent(nbttagcompound2 -> { + String s = cmp.getString("id").get(); + String s1 = convertEntityId(sourceVer, s); + boolean flag; + + if (s1 == null) { + // CraftBukkit - Remove unnecessary warning (occurs when deserializing a Shulker Box item) + // DataInspectorBlockEntity.a.warn("Unable to resolve BlockEntity for ItemInstance: {}", s); + flag = false; + } else { + flag = !nbttagcompound2.contains("id"); + nbttagcompound2.putString("id", s1); + } + + convert(LegacyType.BLOCK_ENTITY, nbttagcompound2, sourceVer, targetVer); + if (flag) { + nbttagcompound2.remove("id"); + } + }); + } + + return cmp; + } + + static { + Map map = DataInspectorBlockEntity.b; + + map.put("minecraft:furnace", "Furnace"); + map.put("minecraft:lit_furnace", "Furnace"); + map.put("minecraft:chest", "Chest"); + map.put("minecraft:trapped_chest", "Chest"); + map.put("minecraft:ender_chest", "EnderChest"); + map.put("minecraft:jukebox", "RecordPlayer"); + map.put("minecraft:dispenser", "Trap"); + map.put("minecraft:dropper", "Dropper"); + map.put("minecraft:sign", "Sign"); + map.put("minecraft:mob_spawner", "MobSpawner"); + map.put("minecraft:noteblock", "Music"); + map.put("minecraft:brewing_stand", "Cauldron"); + map.put("minecraft:enhanting_table", "EnchantTable"); + map.put("minecraft:command_block", "CommandBlock"); + map.put("minecraft:beacon", "Beacon"); + map.put("minecraft:skull", "Skull"); + map.put("minecraft:daylight_detector", "DLDetector"); + map.put("minecraft:hopper", "Hopper"); + map.put("minecraft:banner", "Banner"); + map.put("minecraft:flower_pot", "FlowerPot"); + map.put("minecraft:repeating_command_block", "CommandBlock"); + map.put("minecraft:chain_command_block", "CommandBlock"); + map.put("minecraft:standing_sign", "Sign"); + map.put("minecraft:wall_sign", "Sign"); + map.put("minecraft:piston_head", "Piston"); + map.put("minecraft:daylight_detector_inverted", "DLDetector"); + map.put("minecraft:unpowered_comparator", "Comparator"); + map.put("minecraft:powered_comparator", "Comparator"); + map.put("minecraft:wall_banner", "Banner"); + map.put("minecraft:standing_banner", "Banner"); + map.put("minecraft:structure_block", "Structure"); + map.put("minecraft:end_portal", "Airportal"); + map.put("minecraft:end_gateway", "EndGateway"); + map.put("minecraft:shield", "Shield"); + map = DataInspectorBlockEntity.c; + map.put("minecraft:furnace", "minecraft:furnace"); + map.put("minecraft:lit_furnace", "minecraft:furnace"); + map.put("minecraft:chest", "minecraft:chest"); + map.put("minecraft:trapped_chest", "minecraft:chest"); + map.put("minecraft:ender_chest", "minecraft:enderchest"); + map.put("minecraft:jukebox", "minecraft:jukebox"); + map.put("minecraft:dispenser", "minecraft:dispenser"); + map.put("minecraft:dropper", "minecraft:dropper"); + map.put("minecraft:sign", "minecraft:sign"); + map.put("minecraft:mob_spawner", "minecraft:mob_spawner"); + map.put("minecraft:noteblock", "minecraft:noteblock"); + map.put("minecraft:brewing_stand", "minecraft:brewing_stand"); + map.put("minecraft:enhanting_table", "minecraft:enchanting_table"); + map.put("minecraft:command_block", "minecraft:command_block"); + map.put("minecraft:beacon", "minecraft:beacon"); + map.put("minecraft:skull", "minecraft:skull"); + map.put("minecraft:daylight_detector", "minecraft:daylight_detector"); + map.put("minecraft:hopper", "minecraft:hopper"); + map.put("minecraft:banner", "minecraft:banner"); + map.put("minecraft:flower_pot", "minecraft:flower_pot"); + map.put("minecraft:repeating_command_block", "minecraft:command_block"); + map.put("minecraft:chain_command_block", "minecraft:command_block"); + map.put("minecraft:shulker_box", "minecraft:shulker_box"); + map.put("minecraft:white_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:orange_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:magenta_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:light_blue_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:yellow_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:lime_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:pink_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:gray_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:silver_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:cyan_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:purple_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:blue_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:brown_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:green_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:red_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:black_shulker_box", "minecraft:shulker_box"); + map.put("minecraft:bed", "minecraft:bed"); + map.put("minecraft:standing_sign", "minecraft:sign"); + map.put("minecraft:wall_sign", "minecraft:sign"); + map.put("minecraft:piston_head", "minecraft:piston"); + map.put("minecraft:daylight_detector_inverted", "minecraft:daylight_detector"); + map.put("minecraft:unpowered_comparator", "minecraft:comparator"); + map.put("minecraft:powered_comparator", "minecraft:comparator"); + map.put("minecraft:wall_banner", "minecraft:banner"); + map.put("minecraft:standing_banner", "minecraft:banner"); + map.put("minecraft:structure_block", "minecraft:structure_block"); + map.put("minecraft:end_portal", "minecraft:end_portal"); + map.put("minecraft:end_gateway", "minecraft:end_gateway"); + map.put("minecraft:shield", "minecraft:shield"); + } + } + + private static class DataInspectorEntity implements DataInspector { + + private static final Logger a = LogManager.getLogger(PaperweightDataConverters.class); + + DataInspectorEntity() { + } + + @Override + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + cmp.getCompound("tag").flatMap(nbttagcompound1 -> nbttagcompound1.getCompound("EntityTag")).ifPresent(nbttagcompound2 -> { + String s = cmp.getString("id").orElse(null); + String s1; + + if ("minecraft:armor_stand".equals(s)) { + s1 = sourceVer < 515 ? "ArmorStand" : "minecraft:armor_stand"; + } else { + if (!"minecraft:spawn_egg".equals(s)) { + return; + } + + s1 = nbttagcompound2.getString("id").orElse(null); + } + + boolean flag; + + if (s1 == null) { + DataInspectorEntity.a.warn("Unable to resolve Entity for ItemInstance: {}", s); + flag = false; + } else { + flag = nbttagcompound2.getString("id").isEmpty(); + nbttagcompound2.putString("id", s1); + } + + convert(LegacyType.ENTITY, nbttagcompound2, sourceVer, targetVer); + if (flag) { + nbttagcompound2.remove("id"); + } + }); + + return cmp; + } + } + + + private abstract static class DataInspectorTagged implements DataInspector { + + private final Identifier key; + + DataInspectorTagged(String type) { + this.key = getKey(type); + } + + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + if (cmp.getString("id").isPresent() && this.key.equals(Identifier.parse(cmp.getString("id").get()))) { + cmp = this.inspectChecked(cmp, sourceVer, targetVer); + } + + return cmp; + } + + abstract net.minecraft.nbt.CompoundTag inspectChecked(net.minecraft.nbt.CompoundTag nbttagcompound, int sourceVer, int targetVer); + } + + private static class DataInspectorItemList extends DataInspectorTagged { + + private final String[] keys; + + DataInspectorItemList(String oclass, String... astring) { + super(oclass); + this.keys = astring; + } + + net.minecraft.nbt.CompoundTag inspectChecked(net.minecraft.nbt.CompoundTag nbttagcompound, int sourceVer, int targetVer) { + for (String s : this.keys) { + PaperweightDataConverters.convertItems(nbttagcompound, s, sourceVer, targetVer); + } + + return nbttagcompound; + } + } + + private static class DataInspectorItem extends DataInspectorTagged { + + private final String[] keys; + + DataInspectorItem(String oclass, String... astring) { + super(oclass); + this.keys = astring; + } + + net.minecraft.nbt.CompoundTag inspectChecked(net.minecraft.nbt.CompoundTag nbttagcompound, int sourceVer, int targetVer) { + for (String key : this.keys) { + PaperweightDataConverters.convertItem(nbttagcompound, key, sourceVer, targetVer); + } + + return nbttagcompound; + } + } + + private static class DataConverterMaterialId implements DataConverter { + + private static final String[] materials = new String[2268]; + + DataConverterMaterialId() { + } + + public int getDataVersion() { + return 102; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + cmp.getShort("id").ifPresent(short0 -> { + if (short0 > 0 && short0 < materials.length && materials[short0] != null) { + cmp.putString("id", materials[short0]); + } + }); + + return cmp; + } + + static { + materials[1] = "minecraft:stone"; + materials[2] = "minecraft:grass"; + materials[3] = "minecraft:dirt"; + materials[4] = "minecraft:cobblestone"; + materials[5] = "minecraft:planks"; + materials[6] = "minecraft:sapling"; + materials[7] = "minecraft:bedrock"; + materials[8] = "minecraft:flowing_water"; + materials[9] = "minecraft:water"; + materials[10] = "minecraft:flowing_lava"; + materials[11] = "minecraft:lava"; + materials[12] = "minecraft:sand"; + materials[13] = "minecraft:gravel"; + materials[14] = "minecraft:gold_ore"; + materials[15] = "minecraft:iron_ore"; + materials[16] = "minecraft:coal_ore"; + materials[17] = "minecraft:log"; + materials[18] = "minecraft:leaves"; + materials[19] = "minecraft:sponge"; + materials[20] = "minecraft:glass"; + materials[21] = "minecraft:lapis_ore"; + materials[22] = "minecraft:lapis_block"; + materials[23] = "minecraft:dispenser"; + materials[24] = "minecraft:sandstone"; + materials[25] = "minecraft:noteblock"; + materials[27] = "minecraft:golden_rail"; + materials[28] = "minecraft:detector_rail"; + materials[29] = "minecraft:sticky_piston"; + materials[30] = "minecraft:web"; + materials[31] = "minecraft:tallgrass"; + materials[32] = "minecraft:deadbush"; + materials[33] = "minecraft:piston"; + materials[35] = "minecraft:wool"; + materials[37] = "minecraft:yellow_flower"; + materials[38] = "minecraft:red_flower"; + materials[39] = "minecraft:brown_mushroom"; + materials[40] = "minecraft:red_mushroom"; + materials[41] = "minecraft:gold_block"; + materials[42] = "minecraft:iron_block"; + materials[43] = "minecraft:double_stone_slab"; + materials[44] = "minecraft:stone_slab"; + materials[45] = "minecraft:brick_block"; + materials[46] = "minecraft:tnt"; + materials[47] = "minecraft:bookshelf"; + materials[48] = "minecraft:mossy_cobblestone"; + materials[49] = "minecraft:obsidian"; + materials[50] = "minecraft:torch"; + materials[51] = "minecraft:fire"; + materials[52] = "minecraft:mob_spawner"; + materials[53] = "minecraft:oak_stairs"; + materials[54] = "minecraft:chest"; + materials[56] = "minecraft:diamond_ore"; + materials[57] = "minecraft:diamond_block"; + materials[58] = "minecraft:crafting_table"; + materials[60] = "minecraft:farmland"; + materials[61] = "minecraft:furnace"; + materials[62] = "minecraft:lit_furnace"; + materials[65] = "minecraft:ladder"; + materials[66] = "minecraft:rail"; + materials[67] = "minecraft:stone_stairs"; + materials[69] = "minecraft:lever"; + materials[70] = "minecraft:stone_pressure_plate"; + materials[72] = "minecraft:wooden_pressure_plate"; + materials[73] = "minecraft:redstone_ore"; + materials[76] = "minecraft:redstone_torch"; + materials[77] = "minecraft:stone_button"; + materials[78] = "minecraft:snow_layer"; + materials[79] = "minecraft:ice"; + materials[80] = "minecraft:snow"; + materials[81] = "minecraft:cactus"; + materials[82] = "minecraft:clay"; + materials[84] = "minecraft:jukebox"; + materials[85] = "minecraft:fence"; + materials[86] = "minecraft:pumpkin"; + materials[87] = "minecraft:netherrack"; + materials[88] = "minecraft:soul_sand"; + materials[89] = "minecraft:glowstone"; + materials[90] = "minecraft:portal"; + materials[91] = "minecraft:lit_pumpkin"; + materials[95] = "minecraft:stained_glass"; + materials[96] = "minecraft:trapdoor"; + materials[97] = "minecraft:monster_egg"; + materials[98] = "minecraft:stonebrick"; + materials[99] = "minecraft:brown_mushroom_block"; + materials[100] = "minecraft:red_mushroom_block"; + materials[101] = "minecraft:iron_bars"; + materials[102] = "minecraft:glass_pane"; + materials[103] = "minecraft:melon_block"; + materials[106] = "minecraft:vine"; + materials[107] = "minecraft:fence_gate"; + materials[108] = "minecraft:brick_stairs"; + materials[109] = "minecraft:stone_brick_stairs"; + materials[110] = "minecraft:mycelium"; + materials[111] = "minecraft:waterlily"; + materials[112] = "minecraft:nether_brick"; + materials[113] = "minecraft:nether_brick_fence"; + materials[114] = "minecraft:nether_brick_stairs"; + materials[116] = "minecraft:enchanting_table"; + materials[119] = "minecraft:end_portal"; + materials[120] = "minecraft:end_portal_frame"; + materials[121] = "minecraft:end_stone"; + materials[122] = "minecraft:dragon_egg"; + materials[123] = "minecraft:redstone_lamp"; + materials[125] = "minecraft:double_wooden_slab"; + materials[126] = "minecraft:wooden_slab"; + materials[127] = "minecraft:cocoa"; + materials[128] = "minecraft:sandstone_stairs"; + materials[129] = "minecraft:emerald_ore"; + materials[130] = "minecraft:ender_chest"; + materials[131] = "minecraft:tripwire_hook"; + materials[133] = "minecraft:emerald_block"; + materials[134] = "minecraft:spruce_stairs"; + materials[135] = "minecraft:birch_stairs"; + materials[136] = "minecraft:jungle_stairs"; + materials[137] = "minecraft:command_block"; + materials[138] = "minecraft:beacon"; + materials[139] = "minecraft:cobblestone_wall"; + materials[141] = "minecraft:carrots"; + materials[142] = "minecraft:potatoes"; + materials[143] = "minecraft:wooden_button"; + materials[145] = "minecraft:anvil"; + materials[146] = "minecraft:trapped_chest"; + materials[147] = "minecraft:light_weighted_pressure_plate"; + materials[148] = "minecraft:heavy_weighted_pressure_plate"; + materials[151] = "minecraft:daylight_detector"; + materials[152] = "minecraft:redstone_block"; + materials[153] = "minecraft:quartz_ore"; + materials[154] = "minecraft:hopper"; + materials[155] = "minecraft:quartz_block"; + materials[156] = "minecraft:quartz_stairs"; + materials[157] = "minecraft:activator_rail"; + materials[158] = "minecraft:dropper"; + materials[159] = "minecraft:stained_hardened_clay"; + materials[160] = "minecraft:stained_glass_pane"; + materials[161] = "minecraft:leaves2"; + materials[162] = "minecraft:log2"; + materials[163] = "minecraft:acacia_stairs"; + materials[164] = "minecraft:dark_oak_stairs"; + materials[170] = "minecraft:hay_block"; + materials[171] = "minecraft:carpet"; + materials[172] = "minecraft:hardened_clay"; + materials[173] = "minecraft:coal_block"; + materials[174] = "minecraft:packed_ice"; + materials[175] = "minecraft:double_plant"; + materials[256] = "minecraft:iron_shovel"; + materials[257] = "minecraft:iron_pickaxe"; + materials[258] = "minecraft:iron_axe"; + materials[259] = "minecraft:flint_and_steel"; + materials[260] = "minecraft:apple"; + materials[261] = "minecraft:bow"; + materials[262] = "minecraft:arrow"; + materials[263] = "minecraft:coal"; + materials[264] = "minecraft:diamond"; + materials[265] = "minecraft:iron_ingot"; + materials[266] = "minecraft:gold_ingot"; + materials[267] = "minecraft:iron_sword"; + materials[268] = "minecraft:wooden_sword"; + materials[269] = "minecraft:wooden_shovel"; + materials[270] = "minecraft:wooden_pickaxe"; + materials[271] = "minecraft:wooden_axe"; + materials[272] = "minecraft:stone_sword"; + materials[273] = "minecraft:stone_shovel"; + materials[274] = "minecraft:stone_pickaxe"; + materials[275] = "minecraft:stone_axe"; + materials[276] = "minecraft:diamond_sword"; + materials[277] = "minecraft:diamond_shovel"; + materials[278] = "minecraft:diamond_pickaxe"; + materials[279] = "minecraft:diamond_axe"; + materials[280] = "minecraft:stick"; + materials[281] = "minecraft:bowl"; + materials[282] = "minecraft:mushroom_stew"; + materials[283] = "minecraft:golden_sword"; + materials[284] = "minecraft:golden_shovel"; + materials[285] = "minecraft:golden_pickaxe"; + materials[286] = "minecraft:golden_axe"; + materials[287] = "minecraft:string"; + materials[288] = "minecraft:feather"; + materials[289] = "minecraft:gunpowder"; + materials[290] = "minecraft:wooden_hoe"; + materials[291] = "minecraft:stone_hoe"; + materials[292] = "minecraft:iron_hoe"; + materials[293] = "minecraft:diamond_hoe"; + materials[294] = "minecraft:golden_hoe"; + materials[295] = "minecraft:wheat_seeds"; + materials[296] = "minecraft:wheat"; + materials[297] = "minecraft:bread"; + materials[298] = "minecraft:leather_helmet"; + materials[299] = "minecraft:leather_chestplate"; + materials[300] = "minecraft:leather_leggings"; + materials[301] = "minecraft:leather_boots"; + materials[302] = "minecraft:chainmail_helmet"; + materials[303] = "minecraft:chainmail_chestplate"; + materials[304] = "minecraft:chainmail_leggings"; + materials[305] = "minecraft:chainmail_boots"; + materials[306] = "minecraft:iron_helmet"; + materials[307] = "minecraft:iron_chestplate"; + materials[308] = "minecraft:iron_leggings"; + materials[309] = "minecraft:iron_boots"; + materials[310] = "minecraft:diamond_helmet"; + materials[311] = "minecraft:diamond_chestplate"; + materials[312] = "minecraft:diamond_leggings"; + materials[313] = "minecraft:diamond_boots"; + materials[314] = "minecraft:golden_helmet"; + materials[315] = "minecraft:golden_chestplate"; + materials[316] = "minecraft:golden_leggings"; + materials[317] = "minecraft:golden_boots"; + materials[318] = "minecraft:flint"; + materials[319] = "minecraft:porkchop"; + materials[320] = "minecraft:cooked_porkchop"; + materials[321] = "minecraft:painting"; + materials[322] = "minecraft:golden_apple"; + materials[323] = "minecraft:sign"; + materials[324] = "minecraft:wooden_door"; + materials[325] = "minecraft:bucket"; + materials[326] = "minecraft:water_bucket"; + materials[327] = "minecraft:lava_bucket"; + materials[328] = "minecraft:minecart"; + materials[329] = "minecraft:saddle"; + materials[330] = "minecraft:iron_door"; + materials[331] = "minecraft:redstone"; + materials[332] = "minecraft:snowball"; + materials[333] = "minecraft:boat"; + materials[334] = "minecraft:leather"; + materials[335] = "minecraft:milk_bucket"; + materials[336] = "minecraft:brick"; + materials[337] = "minecraft:clay_ball"; + materials[338] = "minecraft:reeds"; + materials[339] = "minecraft:paper"; + materials[340] = "minecraft:book"; + materials[341] = "minecraft:slime_ball"; + materials[342] = "minecraft:chest_minecart"; + materials[343] = "minecraft:furnace_minecart"; + materials[344] = "minecraft:egg"; + materials[345] = "minecraft:compass"; + materials[346] = "minecraft:fishing_rod"; + materials[347] = "minecraft:clock"; + materials[348] = "minecraft:glowstone_dust"; + materials[349] = "minecraft:fish"; + materials[350] = "minecraft:cooked_fish"; // Paper - cooked_fished -> cooked_fish + materials[351] = "minecraft:dye"; + materials[352] = "minecraft:bone"; + materials[353] = "minecraft:sugar"; + materials[354] = "minecraft:cake"; + materials[355] = "minecraft:bed"; + materials[356] = "minecraft:repeater"; + materials[357] = "minecraft:cookie"; + materials[358] = "minecraft:filled_map"; + materials[359] = "minecraft:shears"; + materials[360] = "minecraft:melon"; + materials[361] = "minecraft:pumpkin_seeds"; + materials[362] = "minecraft:melon_seeds"; + materials[363] = "minecraft:beef"; + materials[364] = "minecraft:cooked_beef"; + materials[365] = "minecraft:chicken"; + materials[366] = "minecraft:cooked_chicken"; + materials[367] = "minecraft:rotten_flesh"; + materials[368] = "minecraft:ender_pearl"; + materials[369] = "minecraft:blaze_rod"; + materials[370] = "minecraft:ghast_tear"; + materials[371] = "minecraft:gold_nugget"; + materials[372] = "minecraft:nether_wart"; + materials[373] = "minecraft:potion"; + materials[374] = "minecraft:glass_bottle"; + materials[375] = "minecraft:spider_eye"; + materials[376] = "minecraft:fermented_spider_eye"; + materials[377] = "minecraft:blaze_powder"; + materials[378] = "minecraft:magma_cream"; + materials[379] = "minecraft:brewing_stand"; + materials[380] = "minecraft:cauldron"; + materials[381] = "minecraft:ender_eye"; + materials[382] = "minecraft:speckled_melon"; + materials[383] = "minecraft:spawn_egg"; + materials[384] = "minecraft:experience_bottle"; + materials[385] = "minecraft:fire_charge"; + materials[386] = "minecraft:writable_book"; + materials[387] = "minecraft:written_book"; + materials[388] = "minecraft:emerald"; + materials[389] = "minecraft:item_frame"; + materials[390] = "minecraft:flower_pot"; + materials[391] = "minecraft:carrot"; + materials[392] = "minecraft:potato"; + materials[393] = "minecraft:baked_potato"; + materials[394] = "minecraft:poisonous_potato"; + materials[395] = "minecraft:map"; + materials[396] = "minecraft:golden_carrot"; + materials[397] = "minecraft:skull"; + materials[398] = "minecraft:carrot_on_a_stick"; + materials[399] = "minecraft:nether_star"; + materials[400] = "minecraft:pumpkin_pie"; + materials[401] = "minecraft:fireworks"; + materials[402] = "minecraft:firework_charge"; + materials[403] = "minecraft:enchanted_book"; + materials[404] = "minecraft:comparator"; + materials[405] = "minecraft:netherbrick"; + materials[406] = "minecraft:quartz"; + materials[407] = "minecraft:tnt_minecart"; + materials[408] = "minecraft:hopper_minecart"; + materials[417] = "minecraft:iron_horse_armor"; + materials[418] = "minecraft:golden_horse_armor"; + materials[419] = "minecraft:diamond_horse_armor"; + materials[420] = "minecraft:lead"; + materials[421] = "minecraft:name_tag"; + materials[422] = "minecraft:command_block_minecart"; + materials[2256] = "minecraft:record_13"; + materials[2257] = "minecraft:record_cat"; + materials[2258] = "minecraft:record_blocks"; + materials[2259] = "minecraft:record_chirp"; + materials[2260] = "minecraft:record_far"; + materials[2261] = "minecraft:record_mall"; + materials[2262] = "minecraft:record_mellohi"; + materials[2263] = "minecraft:record_stal"; + materials[2264] = "minecraft:record_strad"; + materials[2265] = "minecraft:record_ward"; + materials[2266] = "minecraft:record_11"; + materials[2267] = "minecraft:record_wait"; + // Paper start + materials[409] = "minecraft:prismarine_shard"; + materials[410] = "minecraft:prismarine_crystals"; + materials[411] = "minecraft:rabbit"; + materials[412] = "minecraft:cooked_rabbit"; + materials[413] = "minecraft:rabbit_stew"; + materials[414] = "minecraft:rabbit_foot"; + materials[415] = "minecraft:rabbit_hide"; + materials[416] = "minecraft:armor_stand"; + materials[423] = "minecraft:mutton"; + materials[424] = "minecraft:cooked_mutton"; + materials[425] = "minecraft:banner"; + materials[426] = "minecraft:end_crystal"; + materials[427] = "minecraft:spruce_door"; + materials[428] = "minecraft:birch_door"; + materials[429] = "minecraft:jungle_door"; + materials[430] = "minecraft:acacia_door"; + materials[431] = "minecraft:dark_oak_door"; + materials[432] = "minecraft:chorus_fruit"; + materials[433] = "minecraft:chorus_fruit_popped"; + materials[434] = "minecraft:beetroot"; + materials[435] = "minecraft:beetroot_seeds"; + materials[436] = "minecraft:beetroot_soup"; + materials[437] = "minecraft:dragon_breath"; + materials[438] = "minecraft:splash_potion"; + materials[439] = "minecraft:spectral_arrow"; + materials[440] = "minecraft:tipped_arrow"; + materials[441] = "minecraft:lingering_potion"; + materials[442] = "minecraft:shield"; + materials[443] = "minecraft:elytra"; + materials[444] = "minecraft:spruce_boat"; + materials[445] = "minecraft:birch_boat"; + materials[446] = "minecraft:jungle_boat"; + materials[447] = "minecraft:acacia_boat"; + materials[448] = "minecraft:dark_oak_boat"; + materials[449] = "minecraft:totem_of_undying"; + materials[450] = "minecraft:shulker_shell"; + materials[452] = "minecraft:iron_nugget"; + materials[453] = "minecraft:knowledge_book"; + // Paper end + } + } + + private static class DataConverterArmorStand implements DataConverter { + + DataConverterArmorStand() { + } + + public int getDataVersion() { + return 147; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("ArmorStand".equals(cmp.getString("id").orElse(null)) && cmp.getBoolean("Silent").orElse(false) && !cmp.getBoolean("Marker").orElse(false)) { + cmp.remove("Silent"); + } + + return cmp; + } + } + + private static class DataConverterBanner implements DataConverter { + + DataConverterBanner() { + } + + public int getDataVersion() { + return 804; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("minecraft:banner".equals(cmp.getString("id").orElse(null))) { + cmp.getCompound("tag").ifPresent(nbttagcompound1 -> nbttagcompound1.getCompound("BlockEntityTag").ifPresent(nbttagcompound2 -> { + if (nbttagcompound2.getShort("Base").isPresent()) { + cmp.putShort("Damage", (short) (nbttagcompound2.getShort("Base").get() & 15)); + if (nbttagcompound1.getCompound("display").isPresent()) { + CompoundTag nbttagcompound3 = nbttagcompound1.getCompound("display").get(); + + if (nbttagcompound3.getList("Lore").isPresent()) { + ListTag nbttaglist = nbttagcompound3.getList("Lore").get(); + + if (nbttaglist.size() == 1 && "(+NBT)".equals(nbttaglist.getString(0).orElse(null))) { + return; + } + } + } + + nbttagcompound2.remove("Base"); + if (nbttagcompound2.isEmpty()) { + nbttagcompound1.remove("BlockEntityTag"); + } + + if (nbttagcompound1.isEmpty()) { + cmp.remove("tag"); + } + } + })); + } + + return cmp; + } + } + + private static class DataConverterPotionId implements DataConverter { + + private static final String[] potions = new String[128]; + + DataConverterPotionId() { + } + + public int getDataVersion() { + return 102; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("minecraft:potion".equals(cmp.getString("id").orElse(null))) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompoundOrEmpty("tag"); + short short0 = cmp.getShortOr("Damage", (short) 0); + + if (nbttagcompound1.getString("Potion").isEmpty()) { + String s = DataConverterPotionId.potions[short0 & 127]; + + nbttagcompound1.putString("Potion", s == null ? "minecraft:water" : s); + cmp.put("tag", nbttagcompound1); + if ((short0 & 16384) == 16384) { + cmp.putString("id", "minecraft:splash_potion"); + } + } + + if (short0 != 0) { + cmp.putShort("Damage", (short) 0); + } + } + + return cmp; + } + + static { + DataConverterPotionId.potions[0] = "minecraft:water"; + DataConverterPotionId.potions[1] = "minecraft:regeneration"; + DataConverterPotionId.potions[2] = "minecraft:swiftness"; + DataConverterPotionId.potions[3] = "minecraft:fire_resistance"; + DataConverterPotionId.potions[4] = "minecraft:poison"; + DataConverterPotionId.potions[5] = "minecraft:healing"; + DataConverterPotionId.potions[6] = "minecraft:night_vision"; + DataConverterPotionId.potions[7] = null; + DataConverterPotionId.potions[8] = "minecraft:weakness"; + DataConverterPotionId.potions[9] = "minecraft:strength"; + DataConverterPotionId.potions[10] = "minecraft:slowness"; + DataConverterPotionId.potions[11] = "minecraft:leaping"; + DataConverterPotionId.potions[12] = "minecraft:harming"; + DataConverterPotionId.potions[13] = "minecraft:water_breathing"; + DataConverterPotionId.potions[14] = "minecraft:invisibility"; + DataConverterPotionId.potions[15] = null; + DataConverterPotionId.potions[16] = "minecraft:awkward"; + DataConverterPotionId.potions[17] = "minecraft:regeneration"; + DataConverterPotionId.potions[18] = "minecraft:swiftness"; + DataConverterPotionId.potions[19] = "minecraft:fire_resistance"; + DataConverterPotionId.potions[20] = "minecraft:poison"; + DataConverterPotionId.potions[21] = "minecraft:healing"; + DataConverterPotionId.potions[22] = "minecraft:night_vision"; + DataConverterPotionId.potions[23] = null; + DataConverterPotionId.potions[24] = "minecraft:weakness"; + DataConverterPotionId.potions[25] = "minecraft:strength"; + DataConverterPotionId.potions[26] = "minecraft:slowness"; + DataConverterPotionId.potions[27] = "minecraft:leaping"; + DataConverterPotionId.potions[28] = "minecraft:harming"; + DataConverterPotionId.potions[29] = "minecraft:water_breathing"; + DataConverterPotionId.potions[30] = "minecraft:invisibility"; + DataConverterPotionId.potions[31] = null; + DataConverterPotionId.potions[32] = "minecraft:thick"; + DataConverterPotionId.potions[33] = "minecraft:strong_regeneration"; + DataConverterPotionId.potions[34] = "minecraft:strong_swiftness"; + DataConverterPotionId.potions[35] = "minecraft:fire_resistance"; + DataConverterPotionId.potions[36] = "minecraft:strong_poison"; + DataConverterPotionId.potions[37] = "minecraft:strong_healing"; + DataConverterPotionId.potions[38] = "minecraft:night_vision"; + DataConverterPotionId.potions[39] = null; + DataConverterPotionId.potions[40] = "minecraft:weakness"; + DataConverterPotionId.potions[41] = "minecraft:strong_strength"; + DataConverterPotionId.potions[42] = "minecraft:slowness"; + DataConverterPotionId.potions[43] = "minecraft:strong_leaping"; + DataConverterPotionId.potions[44] = "minecraft:strong_harming"; + DataConverterPotionId.potions[45] = "minecraft:water_breathing"; + DataConverterPotionId.potions[46] = "minecraft:invisibility"; + DataConverterPotionId.potions[47] = null; + DataConverterPotionId.potions[48] = null; + DataConverterPotionId.potions[49] = "minecraft:strong_regeneration"; + DataConverterPotionId.potions[50] = "minecraft:strong_swiftness"; + DataConverterPotionId.potions[51] = "minecraft:fire_resistance"; + DataConverterPotionId.potions[52] = "minecraft:strong_poison"; + DataConverterPotionId.potions[53] = "minecraft:strong_healing"; + DataConverterPotionId.potions[54] = "minecraft:night_vision"; + DataConverterPotionId.potions[55] = null; + DataConverterPotionId.potions[56] = "minecraft:weakness"; + DataConverterPotionId.potions[57] = "minecraft:strong_strength"; + DataConverterPotionId.potions[58] = "minecraft:slowness"; + DataConverterPotionId.potions[59] = "minecraft:strong_leaping"; + DataConverterPotionId.potions[60] = "minecraft:strong_harming"; + DataConverterPotionId.potions[61] = "minecraft:water_breathing"; + DataConverterPotionId.potions[62] = "minecraft:invisibility"; + DataConverterPotionId.potions[63] = null; + DataConverterPotionId.potions[64] = "minecraft:mundane"; + DataConverterPotionId.potions[65] = "minecraft:long_regeneration"; + DataConverterPotionId.potions[66] = "minecraft:long_swiftness"; + DataConverterPotionId.potions[67] = "minecraft:long_fire_resistance"; + DataConverterPotionId.potions[68] = "minecraft:long_poison"; + DataConverterPotionId.potions[69] = "minecraft:healing"; + DataConverterPotionId.potions[70] = "minecraft:long_night_vision"; + DataConverterPotionId.potions[71] = null; + DataConverterPotionId.potions[72] = "minecraft:long_weakness"; + DataConverterPotionId.potions[73] = "minecraft:long_strength"; + DataConverterPotionId.potions[74] = "minecraft:long_slowness"; + DataConverterPotionId.potions[75] = "minecraft:long_leaping"; + DataConverterPotionId.potions[76] = "minecraft:harming"; + DataConverterPotionId.potions[77] = "minecraft:long_water_breathing"; + DataConverterPotionId.potions[78] = "minecraft:long_invisibility"; + DataConverterPotionId.potions[79] = null; + DataConverterPotionId.potions[80] = "minecraft:awkward"; + DataConverterPotionId.potions[81] = "minecraft:long_regeneration"; + DataConverterPotionId.potions[82] = "minecraft:long_swiftness"; + DataConverterPotionId.potions[83] = "minecraft:long_fire_resistance"; + DataConverterPotionId.potions[84] = "minecraft:long_poison"; + DataConverterPotionId.potions[85] = "minecraft:healing"; + DataConverterPotionId.potions[86] = "minecraft:long_night_vision"; + DataConverterPotionId.potions[87] = null; + DataConverterPotionId.potions[88] = "minecraft:long_weakness"; + DataConverterPotionId.potions[89] = "minecraft:long_strength"; + DataConverterPotionId.potions[90] = "minecraft:long_slowness"; + DataConverterPotionId.potions[91] = "minecraft:long_leaping"; + DataConverterPotionId.potions[92] = "minecraft:harming"; + DataConverterPotionId.potions[93] = "minecraft:long_water_breathing"; + DataConverterPotionId.potions[94] = "minecraft:long_invisibility"; + DataConverterPotionId.potions[95] = null; + DataConverterPotionId.potions[96] = "minecraft:thick"; + DataConverterPotionId.potions[97] = "minecraft:regeneration"; + DataConverterPotionId.potions[98] = "minecraft:swiftness"; + DataConverterPotionId.potions[99] = "minecraft:long_fire_resistance"; + DataConverterPotionId.potions[100] = "minecraft:poison"; + DataConverterPotionId.potions[101] = "minecraft:strong_healing"; + DataConverterPotionId.potions[102] = "minecraft:long_night_vision"; + DataConverterPotionId.potions[103] = null; + DataConverterPotionId.potions[104] = "minecraft:long_weakness"; + DataConverterPotionId.potions[105] = "minecraft:strength"; + DataConverterPotionId.potions[106] = "minecraft:long_slowness"; + DataConverterPotionId.potions[107] = "minecraft:leaping"; + DataConverterPotionId.potions[108] = "minecraft:strong_harming"; + DataConverterPotionId.potions[109] = "minecraft:long_water_breathing"; + DataConverterPotionId.potions[110] = "minecraft:long_invisibility"; + DataConverterPotionId.potions[111] = null; + DataConverterPotionId.potions[112] = null; + DataConverterPotionId.potions[113] = "minecraft:regeneration"; + DataConverterPotionId.potions[114] = "minecraft:swiftness"; + DataConverterPotionId.potions[115] = "minecraft:long_fire_resistance"; + DataConverterPotionId.potions[116] = "minecraft:poison"; + DataConverterPotionId.potions[117] = "minecraft:strong_healing"; + DataConverterPotionId.potions[118] = "minecraft:long_night_vision"; + DataConverterPotionId.potions[119] = null; + DataConverterPotionId.potions[120] = "minecraft:long_weakness"; + DataConverterPotionId.potions[121] = "minecraft:strength"; + DataConverterPotionId.potions[122] = "minecraft:long_slowness"; + DataConverterPotionId.potions[123] = "minecraft:leaping"; + DataConverterPotionId.potions[124] = "minecraft:strong_harming"; + DataConverterPotionId.potions[125] = "minecraft:long_water_breathing"; + DataConverterPotionId.potions[126] = "minecraft:long_invisibility"; + DataConverterPotionId.potions[127] = null; + } + } + + private static class DataConverterSpawnEgg implements DataConverter { + + private static final String[] eggs = new String[256]; + + DataConverterSpawnEgg() { + } + + public int getDataVersion() { + return 105; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("minecraft:spawn_egg".equals(cmp.getString("id").orElse(null))) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompoundOrEmpty("tag"); + net.minecraft.nbt.CompoundTag nbttagcompound2 = nbttagcompound1.getCompoundOrEmpty("EntityTag"); + short short0 = cmp.getShortOr("Damage", (short) 0); + + if (nbttagcompound2.getString("id").isEmpty()) { + String s = DataConverterSpawnEgg.eggs[short0 & 255]; + + if (s != null) { + nbttagcompound2.putString("id", s); + nbttagcompound1.put("EntityTag", nbttagcompound2); + cmp.put("tag", nbttagcompound1); + } + } + + if (short0 != 0) { + cmp.putShort("Damage", (short) 0); + } + } + + return cmp; + } + + static { + + DataConverterSpawnEgg.eggs[1] = "Item"; + DataConverterSpawnEgg.eggs[2] = "XPOrb"; + DataConverterSpawnEgg.eggs[7] = "ThrownEgg"; + DataConverterSpawnEgg.eggs[8] = "LeashKnot"; + DataConverterSpawnEgg.eggs[9] = "Painting"; + DataConverterSpawnEgg.eggs[10] = "Arrow"; + DataConverterSpawnEgg.eggs[11] = "Snowball"; + DataConverterSpawnEgg.eggs[12] = "Fireball"; + DataConverterSpawnEgg.eggs[13] = "SmallFireball"; + DataConverterSpawnEgg.eggs[14] = "ThrownEnderpearl"; + DataConverterSpawnEgg.eggs[15] = "EyeOfEnderSignal"; + DataConverterSpawnEgg.eggs[16] = "ThrownPotion"; + DataConverterSpawnEgg.eggs[17] = "ThrownExpBottle"; + DataConverterSpawnEgg.eggs[18] = "ItemFrame"; + DataConverterSpawnEgg.eggs[19] = "WitherSkull"; + DataConverterSpawnEgg.eggs[20] = "PrimedTnt"; + DataConverterSpawnEgg.eggs[21] = "FallingSand"; + DataConverterSpawnEgg.eggs[22] = "FireworksRocketEntity"; + DataConverterSpawnEgg.eggs[23] = "TippedArrow"; + DataConverterSpawnEgg.eggs[24] = "SpectralArrow"; + DataConverterSpawnEgg.eggs[25] = "ShulkerBullet"; + DataConverterSpawnEgg.eggs[26] = "DragonFireball"; + DataConverterSpawnEgg.eggs[30] = "ArmorStand"; + DataConverterSpawnEgg.eggs[41] = "Boat"; + DataConverterSpawnEgg.eggs[42] = "MinecartRideable"; + DataConverterSpawnEgg.eggs[43] = "MinecartChest"; + DataConverterSpawnEgg.eggs[44] = "MinecartFurnace"; + DataConverterSpawnEgg.eggs[45] = "MinecartTNT"; + DataConverterSpawnEgg.eggs[46] = "MinecartHopper"; + DataConverterSpawnEgg.eggs[47] = "MinecartSpawner"; + DataConverterSpawnEgg.eggs[40] = "MinecartCommandBlock"; + DataConverterSpawnEgg.eggs[48] = "Mob"; + DataConverterSpawnEgg.eggs[49] = "Monster"; + DataConverterSpawnEgg.eggs[50] = "Creeper"; + DataConverterSpawnEgg.eggs[51] = "Skeleton"; + DataConverterSpawnEgg.eggs[52] = "Spider"; + DataConverterSpawnEgg.eggs[53] = "Giant"; + DataConverterSpawnEgg.eggs[54] = "Zombie"; + DataConverterSpawnEgg.eggs[55] = "Slime"; + DataConverterSpawnEgg.eggs[56] = "Ghast"; + DataConverterSpawnEgg.eggs[57] = "PigZombie"; + DataConverterSpawnEgg.eggs[58] = "Enderman"; + DataConverterSpawnEgg.eggs[59] = "CaveSpider"; + DataConverterSpawnEgg.eggs[60] = "Silverfish"; + DataConverterSpawnEgg.eggs[61] = "Blaze"; + DataConverterSpawnEgg.eggs[62] = "LavaSlime"; + DataConverterSpawnEgg.eggs[63] = "EnderDragon"; + DataConverterSpawnEgg.eggs[64] = "WitherBoss"; + DataConverterSpawnEgg.eggs[65] = "Bat"; + DataConverterSpawnEgg.eggs[66] = "Witch"; + DataConverterSpawnEgg.eggs[67] = "Endermite"; + DataConverterSpawnEgg.eggs[68] = "Guardian"; + DataConverterSpawnEgg.eggs[69] = "Shulker"; + DataConverterSpawnEgg.eggs[90] = "Pig"; + DataConverterSpawnEgg.eggs[91] = "Sheep"; + DataConverterSpawnEgg.eggs[92] = "Cow"; + DataConverterSpawnEgg.eggs[93] = "Chicken"; + DataConverterSpawnEgg.eggs[94] = "Squid"; + DataConverterSpawnEgg.eggs[95] = "Wolf"; + DataConverterSpawnEgg.eggs[96] = "MushroomCow"; + DataConverterSpawnEgg.eggs[97] = "SnowMan"; + DataConverterSpawnEgg.eggs[98] = "Ozelot"; + DataConverterSpawnEgg.eggs[99] = "VillagerGolem"; + DataConverterSpawnEgg.eggs[100] = "EntityHorse"; + DataConverterSpawnEgg.eggs[101] = "Rabbit"; + DataConverterSpawnEgg.eggs[120] = "Villager"; + DataConverterSpawnEgg.eggs[200] = "EnderCrystal"; + } + } + + private static class DataConverterMinecart implements DataConverter { + + private static final List a = List.of("MinecartRideable", "MinecartChest", "MinecartFurnace", "MinecartTNT", "MinecartSpawner", "MinecartHopper", "MinecartCommandBlock"); + + DataConverterMinecart() { + } + + public int getDataVersion() { + return 106; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("Minecart".equals(cmp.getString("id").orElse(null))) { + String s = "MinecartRideable"; + int i = cmp.getIntOr("Type", 0); + + if (i > 0 && i < DataConverterMinecart.a.size()) { + s = DataConverterMinecart.a.get(i); + } + + cmp.putString("id", s); + cmp.remove("Type"); + } + + return cmp; + } + } + + private static class DataConverterMobSpawner implements DataConverter { + + DataConverterMobSpawner() { + } + + public int getDataVersion() { + return 107; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("MobSpawner".equals(cmp.getString("id").orElse(null))) { + cmp.getString("EntityId").ifPresent(s -> { + CompoundTag nbttagcompound1 = cmp.getCompoundOrEmpty("SpawnData"); + + nbttagcompound1.putString("id", s.isEmpty() ? "Pig" : s); + cmp.put("SpawnData", nbttagcompound1); + cmp.remove("EntityId"); + }); + + cmp.getList("SpawnPotentials").ifPresent(nbttaglist -> { + for (int i = 0; i < nbttaglist.size(); ++i) { + CompoundTag nbttagcompound2 = nbttaglist.getCompoundOrEmpty(i); + + if (nbttagcompound2.getString("Type").isPresent()) { + CompoundTag nbttagcompound3 = nbttagcompound2.getCompoundOrEmpty("Properties"); + + nbttagcompound3.putString("id", nbttagcompound2.getString("Type").get()); + nbttagcompound2.put("Entity", nbttagcompound3); + nbttagcompound2.remove("Type"); + nbttagcompound2.remove("Properties"); + } + } + }); + + } + + return cmp; + } + } + + private static class DataConverterUUID implements DataConverter { + + DataConverterUUID() { + } + + public int getDataVersion() { + return 108; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + cmp.getString("UUID").ifPresent(uuid -> { + cmp.putIntArray("UUID", UUIDUtil.uuidToIntArray(UUID.fromString(uuid))); + }); + + return cmp; + } + } + + private static class DataConverterHealth implements DataConverter { + + private static final Set a = Sets.newHashSet("ArmorStand", "Bat", "Blaze", "CaveSpider", "Chicken", "Cow", "Creeper", "EnderDragon", "Enderman", "Endermite", "EntityHorse", "Ghast", "Giant", "Guardian", "LavaSlime", "MushroomCow", "Ozelot", "Pig", "PigZombie", "Rabbit", "Sheep", "Shulker", "Silverfish", "Skeleton", "Slime", "SnowMan", "Spider", "Squid", "Villager", "VillagerGolem", "Witch", "WitherBoss", "Wolf", "Zombie"); + + DataConverterHealth() { + } + + public int getDataVersion() { + return 109; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if (DataConverterHealth.a.contains(cmp.getString("id").orElse(null))) { + float f; + + if (cmp.getFloat("HealF").isPresent()) { + f = cmp.getFloat("HealF").get(); + cmp.remove("HealF"); + } else { + if (cmp.getFloat("Health").isEmpty()) { + return cmp; + } + + f = cmp.getFloat("Health").get(); + } + + cmp.putFloat("Health", f); + } + + return cmp; + } + } + + private static class DataConverterSaddle implements DataConverter { + + DataConverterSaddle() { + } + + public int getDataVersion() { + return 110; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("EntityHorse".equals(cmp.getString("id").orElse(null)) && cmp.getCompound("SaddleItem").isEmpty() && cmp.getBoolean("Saddle").orElse(false)) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = new net.minecraft.nbt.CompoundTag(); + + nbttagcompound1.putString("id", "minecraft:saddle"); + nbttagcompound1.putByte("Count", (byte) 1); + nbttagcompound1.putShort("Damage", (short) 0); + cmp.put("SaddleItem", nbttagcompound1); + cmp.remove("Saddle"); + } + + return cmp; + } + } + + private static class DataConverterHanging implements DataConverter { + + DataConverterHanging() { + } + + public int getDataVersion() { + return 111; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + String s = cmp.getString("id").orElse(null); + boolean flag = "Painting".equals(s); + boolean flag1 = "ItemFrame".equals(s); + + if ((flag || flag1) && cmp.getByte("Facing").isEmpty()) { + Direction enumdirection; + + if (cmp.getByte("Direction").isPresent()) { + enumdirection = Direction.from2DDataValue(cmp.getByte("Direction").get()); + cmp.putInt("TileX", cmp.getIntOr("TileX", 0) + enumdirection.getStepX()); + cmp.putInt("TileY", cmp.getIntOr("TileY", 0) + enumdirection.getStepY()); + cmp.putInt("TileZ", cmp.getIntOr("TileZ", 0) + enumdirection.getStepZ()); + cmp.remove("Direction"); + if (flag1 && cmp.getByte("ItemRotation").isPresent()) { + cmp.putByte("ItemRotation", (byte) (cmp.getByte("ItemRotation").get() * 2)); + } + } else { + enumdirection = Direction.from2DDataValue(cmp.getByte("Dir").get()); + cmp.remove("Dir"); + } + + cmp.putByte("Facing", (byte) enumdirection.get2DDataValue()); + } + + return cmp; + } + } + + private static class DataConverterDropChances implements DataConverter { + + DataConverterDropChances() { + } + + public int getDataVersion() { + return 113; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + cmp.getList("HandDropChances").ifPresent(nbttaglist -> { + if (nbttaglist.size() == 2 && nbttaglist.getFloatOr(0, 0.0F) == 0.0F && nbttaglist.getFloatOr(1, 0.0F) == 0.0F) { + cmp.remove("HandDropChances"); + } + }); + + cmp.getList("ArmorDropChances").ifPresent(nbttaglist -> { + if (nbttaglist.size() == 4 && nbttaglist.getFloatOr(0, 0.0F) == 0.0F && nbttaglist.getFloatOr(1, 0.0F) == 0.0F && nbttaglist.getFloatOr(2, 0.0F) == 0.0F && nbttaglist.getFloatOr(3, 0.0F) == 0.0F) { + cmp.remove("ArmorDropChances"); + } + }); + + return cmp; + } + } + + private static class DataConverterRiding implements DataConverter { + + DataConverterRiding() { + } + + public int getDataVersion() { + return 135; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + while (cmp.getCompound("Riding").isPresent()) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = this.b(cmp); + + this.convert(cmp, nbttagcompound1); + cmp = nbttagcompound1; + } + + return cmp; + } + + protected void convert(net.minecraft.nbt.CompoundTag nbttagcompound, net.minecraft.nbt.CompoundTag nbttagcompound1) { + ListTag nbttaglist = new ListTag(); + + nbttaglist.add(nbttagcompound); + nbttagcompound1.put("Passengers", nbttaglist); + } + + protected net.minecraft.nbt.CompoundTag b(net.minecraft.nbt.CompoundTag nbttagcompound) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = nbttagcompound.getCompoundOrEmpty("Riding"); + + nbttagcompound.remove("Riding"); + return nbttagcompound1; + } + } + + private static class DataConverterBook implements DataConverter { + + DataConverterBook() { + } + + public int getDataVersion() { + return 165; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("minecraft:written_book".equals(cmp.getString("id").orElse(null))) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompoundOrEmpty("tag"); + + nbttagcompound1.getList("pages").ifPresent(nbttaglist -> { + for (int i = 0; i < nbttaglist.size(); ++i) { + String s = nbttaglist.getString(i).orElse(null); + Object object = null; + + if (!"null".equals(s) && !Strings.isNullOrEmpty(s)) { + if ((s.charAt(0) != 34 || s.charAt(s.length() - 1) != 34) && (s.charAt(0) != 123 || s.charAt(s.length() - 1) != 125)) { + object = Component.literal(s); + } else { + try { + object = GsonHelper.fromJson(DataConverterSignText.a, s, Component.class); + if (object == null) { + object = Component.literal(""); + } + } catch (JsonParseException jsonparseexception) { + ; + } + + if (object == null) { + try { + object = ComponentConverter.Serializer.fromJson(s, MinecraftServer.getServer().registryAccess()); + } catch (JsonParseException jsonparseexception1) { + ; + } + } + + if (object == null) { + try { + object = ComponentConverter.Serializer.fromJsonLenient(s, MinecraftServer.getServer().registryAccess()); + } catch (JsonParseException jsonparseexception2) { + ; + } + } + + if (object == null) { + object = Component.literal(s); + } + } + } else { + object = Component.literal(""); + } + + nbttaglist.set(i, StringTag.valueOf(ComponentConverter.Serializer.toJson((Component) object, MinecraftServer.getServer().registryAccess()))); + } + + nbttagcompound1.put("pages", nbttaglist); + }); + } + + return cmp; + } + } + + private static class DataConverterCookedFish implements DataConverter { + + private static final Identifier a = Identifier.parse("cooked_fished"); + + DataConverterCookedFish() { + } + + public int getDataVersion() { + return 502; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if (cmp.getString("id").isPresent() && DataConverterCookedFish.a.equals(Identifier.parse(cmp.getString("id").get()))) { + cmp.putString("id", "minecraft:cooked_fish"); + } + + return cmp; + } + } + + private static class DataConverterZombie implements DataConverter { + + private static final Random a = new Random(); + + DataConverterZombie() { + } + + public int getDataVersion() { + return 502; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("Zombie".equals(cmp.getString("id").orElse(null)) && cmp.getBoolean("IsVillager").orElse(false)) { + if (!cmp.contains("ZombieType")) { + int i = -1; + + i = cmp.getInt("VillagerProfession").flatMap(profession -> { + try { + return Optional.of(this.convert(profession)); + } catch (RuntimeException runtimeexception) { + return Optional.empty(); + } + }).orElse(i); + + if (i == -1) { + i = this.convert(DataConverterZombie.a.nextInt(6)); + } + + cmp.putInt("ZombieType", i); + } + + cmp.remove("IsVillager"); + } + + return cmp; + } + + private int convert(int i) { + return i >= 0 && i < 6 ? i : -1; + } + } + + private static class DataConverterVBO implements DataConverter { + + DataConverterVBO() { + } + + public int getDataVersion() { + return 505; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + cmp.putString("useVbo", "true"); + return cmp; + } + } + + private static class DataConverterGuardian implements DataConverter { + + DataConverterGuardian() { + } + + public int getDataVersion() { + return 700; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("Guardian".equals(cmp.getString("id").orElse(null))) { + if (cmp.getBoolean("Elder").orElse(false)) { + cmp.putString("id", "ElderGuardian"); + } + + cmp.remove("Elder"); + } + + return cmp; + } + } + + private static class DataConverterSkeleton implements DataConverter { + + DataConverterSkeleton() { + } + + public int getDataVersion() { + return 701; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + String s = cmp.getString("id").orElse(null); + + if ("Skeleton".equals(s)) { + int i = cmp.getIntOr("SkeletonType", 0); + + if (i == 1) { + cmp.putString("id", "WitherSkeleton"); + } else if (i == 2) { + cmp.putString("id", "Stray"); + } + + cmp.remove("SkeletonType"); + } + + return cmp; + } + } + + private static class DataConverterZombieType implements DataConverter { + + DataConverterZombieType() { + } + + public int getDataVersion() { + return 702; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("Zombie".equals(cmp.getString("id").orElse(null))) { + int i = cmp.getIntOr("ZombieType", 0); + + switch (i) { + case 1: + case 2: + case 3: + case 4: + case 5: + cmp.putString("id", "ZombieVillager"); + cmp.putInt("Profession", i - 1); + break; + case 6: + cmp.putString("id", "Husk"); + case 0: + default: + break; + } + + cmp.remove("ZombieType"); + } + + return cmp; + } + } + + private static class DataConverterHorse implements DataConverter { + + DataConverterHorse() { + } + + public int getDataVersion() { + return 703; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("EntityHorse".equals(cmp.getString("id").orElse(null))) { + int i = cmp.getIntOr("Type", 0); + + switch (i) { + case 1: + cmp.putString("id", "Donkey"); + break; + + case 2: + cmp.putString("id", "Mule"); + break; + + case 3: + cmp.putString("id", "ZombieHorse"); + break; + + case 4: + cmp.putString("id", "SkeletonHorse"); + break; + + case 0: + default: + cmp.putString("id", "Horse"); + break; + } + + cmp.remove("Type"); + } + + return cmp; + } + } + + private static class DataConverterTileEntity implements DataConverter { + + private static final Map a = Maps.newHashMap(); + + DataConverterTileEntity() { + } + + public int getDataVersion() { + return 704; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + String s = DataConverterTileEntity.a.get(cmp.getString("id").orElse(null)); + + if (s != null) { + cmp.putString("id", s); + } + + return cmp; + } + + static { + DataConverterTileEntity.a.put("Airportal", "minecraft:end_portal"); + DataConverterTileEntity.a.put("Banner", "minecraft:banner"); + DataConverterTileEntity.a.put("Beacon", "minecraft:beacon"); + DataConverterTileEntity.a.put("Cauldron", "minecraft:brewing_stand"); + DataConverterTileEntity.a.put("Chest", "minecraft:chest"); + DataConverterTileEntity.a.put("Comparator", "minecraft:comparator"); + DataConverterTileEntity.a.put("Control", "minecraft:command_block"); + DataConverterTileEntity.a.put("DLDetector", "minecraft:daylight_detector"); + DataConverterTileEntity.a.put("Dropper", "minecraft:dropper"); + DataConverterTileEntity.a.put("EnchantTable", "minecraft:enchanting_table"); + DataConverterTileEntity.a.put("EndGateway", "minecraft:end_gateway"); + DataConverterTileEntity.a.put("EnderChest", "minecraft:ender_chest"); + DataConverterTileEntity.a.put("FlowerPot", "minecraft:flower_pot"); + DataConverterTileEntity.a.put("Furnace", "minecraft:furnace"); + DataConverterTileEntity.a.put("Hopper", "minecraft:hopper"); + DataConverterTileEntity.a.put("MobSpawner", "minecraft:mob_spawner"); + DataConverterTileEntity.a.put("Music", "minecraft:noteblock"); + DataConverterTileEntity.a.put("Piston", "minecraft:piston"); + DataConverterTileEntity.a.put("RecordPlayer", "minecraft:jukebox"); + DataConverterTileEntity.a.put("Sign", "minecraft:sign"); + DataConverterTileEntity.a.put("Skull", "minecraft:skull"); + DataConverterTileEntity.a.put("Structure", "minecraft:structure_block"); + DataConverterTileEntity.a.put("Trap", "minecraft:dispenser"); + } + } + + private static class DataConverterEntity implements DataConverter { + + private static final Map a = Maps.newHashMap(); + + DataConverterEntity() { + } + + public int getDataVersion() { + return 704; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + String s = DataConverterEntity.a.get(cmp.getString("id").orElse(null)); + + if (s != null) { + cmp.putString("id", s); + } + + return cmp; + } + + static { + DataConverterEntity.a.put("AreaEffectCloud", "minecraft:area_effect_cloud"); + DataConverterEntity.a.put("ArmorStand", "minecraft:armor_stand"); + DataConverterEntity.a.put("Arrow", "minecraft:arrow"); + DataConverterEntity.a.put("Bat", "minecraft:bat"); + DataConverterEntity.a.put("Blaze", "minecraft:blaze"); + DataConverterEntity.a.put("Boat", "minecraft:boat"); + DataConverterEntity.a.put("CaveSpider", "minecraft:cave_spider"); + DataConverterEntity.a.put("Chicken", "minecraft:chicken"); + DataConverterEntity.a.put("Cow", "minecraft:cow"); + DataConverterEntity.a.put("Creeper", "minecraft:creeper"); + DataConverterEntity.a.put("Donkey", "minecraft:donkey"); + DataConverterEntity.a.put("DragonFireball", "minecraft:dragon_fireball"); + DataConverterEntity.a.put("ElderGuardian", "minecraft:elder_guardian"); + DataConverterEntity.a.put("EnderCrystal", "minecraft:ender_crystal"); + DataConverterEntity.a.put("EnderDragon", "minecraft:ender_dragon"); + DataConverterEntity.a.put("Enderman", "minecraft:enderman"); + DataConverterEntity.a.put("Endermite", "minecraft:endermite"); + DataConverterEntity.a.put("EyeOfEnderSignal", "minecraft:eye_of_ender_signal"); + DataConverterEntity.a.put("FallingSand", "minecraft:falling_block"); + DataConverterEntity.a.put("Fireball", "minecraft:fireball"); + DataConverterEntity.a.put("FireworksRocketEntity", "minecraft:fireworks_rocket"); + DataConverterEntity.a.put("Ghast", "minecraft:ghast"); + DataConverterEntity.a.put("Giant", "minecraft:giant"); + DataConverterEntity.a.put("Guardian", "minecraft:guardian"); + DataConverterEntity.a.put("Horse", "minecraft:horse"); + DataConverterEntity.a.put("Husk", "minecraft:husk"); + DataConverterEntity.a.put("Item", "minecraft:item"); + DataConverterEntity.a.put("ItemFrame", "minecraft:item_frame"); + DataConverterEntity.a.put("LavaSlime", "minecraft:magma_cube"); + DataConverterEntity.a.put("LeashKnot", "minecraft:leash_knot"); + DataConverterEntity.a.put("MinecartChest", "minecraft:chest_minecart"); + DataConverterEntity.a.put("MinecartCommandBlock", "minecraft:commandblock_minecart"); + DataConverterEntity.a.put("MinecartFurnace", "minecraft:furnace_minecart"); + DataConverterEntity.a.put("MinecartHopper", "minecraft:hopper_minecart"); + DataConverterEntity.a.put("MinecartRideable", "minecraft:minecart"); + DataConverterEntity.a.put("MinecartSpawner", "minecraft:spawner_minecart"); + DataConverterEntity.a.put("MinecartTNT", "minecraft:tnt_minecart"); + DataConverterEntity.a.put("Mule", "minecraft:mule"); + DataConverterEntity.a.put("MushroomCow", "minecraft:mooshroom"); + DataConverterEntity.a.put("Ozelot", "minecraft:ocelot"); + DataConverterEntity.a.put("Painting", "minecraft:painting"); + DataConverterEntity.a.put("Pig", "minecraft:pig"); + DataConverterEntity.a.put("PigZombie", "minecraft:zombie_pigman"); + DataConverterEntity.a.put("PolarBear", "minecraft:polar_bear"); + DataConverterEntity.a.put("PrimedTnt", "minecraft:tnt"); + DataConverterEntity.a.put("Rabbit", "minecraft:rabbit"); + DataConverterEntity.a.put("Sheep", "minecraft:sheep"); + DataConverterEntity.a.put("Shulker", "minecraft:shulker"); + DataConverterEntity.a.put("ShulkerBullet", "minecraft:shulker_bullet"); + DataConverterEntity.a.put("Silverfish", "minecraft:silverfish"); + DataConverterEntity.a.put("Skeleton", "minecraft:skeleton"); + DataConverterEntity.a.put("SkeletonHorse", "minecraft:skeleton_horse"); + DataConverterEntity.a.put("Slime", "minecraft:slime"); + DataConverterEntity.a.put("SmallFireball", "minecraft:small_fireball"); + DataConverterEntity.a.put("SnowMan", "minecraft:snowman"); + DataConverterEntity.a.put("Snowball", "minecraft:snowball"); + DataConverterEntity.a.put("SpectralArrow", "minecraft:spectral_arrow"); + DataConverterEntity.a.put("Spider", "minecraft:spider"); + DataConverterEntity.a.put("Squid", "minecraft:squid"); + DataConverterEntity.a.put("Stray", "minecraft:stray"); + DataConverterEntity.a.put("ThrownEgg", "minecraft:egg"); + DataConverterEntity.a.put("ThrownEnderpearl", "minecraft:ender_pearl"); + DataConverterEntity.a.put("ThrownExpBottle", "minecraft:xp_bottle"); + DataConverterEntity.a.put("ThrownPotion", "minecraft:potion"); + DataConverterEntity.a.put("Villager", "minecraft:villager"); + DataConverterEntity.a.put("VillagerGolem", "minecraft:villager_golem"); + DataConverterEntity.a.put("Witch", "minecraft:witch"); + DataConverterEntity.a.put("WitherBoss", "minecraft:wither"); + DataConverterEntity.a.put("WitherSkeleton", "minecraft:wither_skeleton"); + DataConverterEntity.a.put("WitherSkull", "minecraft:wither_skull"); + DataConverterEntity.a.put("Wolf", "minecraft:wolf"); + DataConverterEntity.a.put("XPOrb", "minecraft:xp_orb"); + DataConverterEntity.a.put("Zombie", "minecraft:zombie"); + DataConverterEntity.a.put("ZombieHorse", "minecraft:zombie_horse"); + DataConverterEntity.a.put("ZombieVillager", "minecraft:zombie_villager"); + } + } + + private static class DataConverterPotionWater implements DataConverter { + + DataConverterPotionWater() { + } + + public int getDataVersion() { + return 806; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + String s = cmp.getString("id").orElse(null); + + if ("minecraft:potion".equals(s) || "minecraft:splash_potion".equals(s) || "minecraft:lingering_potion".equals(s) || "minecraft:tipped_arrow".equals(s)) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompoundOrEmpty("tag"); + + if (nbttagcompound1.getString("Potion").isEmpty()) { + nbttagcompound1.putString("Potion", "minecraft:water"); + } + + if (cmp.getCompound("tag").isEmpty()) { + cmp.put("tag", nbttagcompound1); + } + } + + return cmp; + } + } + + private static class DataConverterShulker implements DataConverter { + + DataConverterShulker() { + } + + public int getDataVersion() { + return 808; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("minecraft:shulker".equals(cmp.getString("id").orElse(null)) && cmp.getByte("Color").isEmpty()) { + cmp.putByte("Color", (byte) 10); + } + + return cmp; + } + } + + private static class DataConverterShulkerBoxItem implements DataConverter { + + public static final String[] a = new String[] { "minecraft:white_shulker_box", "minecraft:orange_shulker_box", "minecraft:magenta_shulker_box", "minecraft:light_blue_shulker_box", "minecraft:yellow_shulker_box", "minecraft:lime_shulker_box", "minecraft:pink_shulker_box", "minecraft:gray_shulker_box", "minecraft:silver_shulker_box", "minecraft:cyan_shulker_box", "minecraft:purple_shulker_box", "minecraft:blue_shulker_box", "minecraft:brown_shulker_box", "minecraft:green_shulker_box", "minecraft:red_shulker_box", "minecraft:black_shulker_box" }; + + DataConverterShulkerBoxItem() { + } + + public int getDataVersion() { + return 813; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("minecraft:shulker_box".equals(cmp.getString("id").orElse(null)) ) { + cmp.getCompound("tag").ifPresent(nbttagcompound1 -> { + nbttagcompound1.getCompound("BlockEntityTag").ifPresent(nbttagcompound2 -> { + if (nbttagcompound2.getList("Items").map(ListTag::isEmpty).orElse(true)) { + nbttagcompound2.remove("Items"); + } + + int i = nbttagcompound2.getIntOr("Color", 0); + + nbttagcompound2.remove("Color"); + if (nbttagcompound2.isEmpty()) { + nbttagcompound1.remove("BlockEntityTag"); + } + + if (nbttagcompound1.isEmpty()) { + cmp.remove("tag"); + } + + cmp.putString("id", DataConverterShulkerBoxItem.a[i % 16]); + }); + }); + } + + return cmp; + } + } + + private static class DataConverterShulkerBoxBlock implements DataConverter { + + DataConverterShulkerBoxBlock() { + } + + public int getDataVersion() { + return 813; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("minecraft:shulker".equals(cmp.getString("id").orElse(null))) { + cmp.remove("Color"); + } + + return cmp; + } + } + + private static class DataConverterLang implements DataConverter { + + DataConverterLang() { + } + + public int getDataVersion() { + return 816; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + cmp.getString("lang").ifPresent(lang -> { + cmp.putString("lang", lang.toLowerCase(Locale.ROOT)); + }); + + return cmp; + } + } + + private static class DataConverterTotem implements DataConverter { + + DataConverterTotem() { + } + + public int getDataVersion() { + return 820; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("minecraft:totem".equals(cmp.getString("id").orElse(null))) { + cmp.putString("id", "minecraft:totem_of_undying"); + } + + return cmp; + } + } + + private static class DataConverterBedBlock implements DataConverter { + + private static final Logger a = LogManager.getLogger(PaperweightDataConverters.class); + + DataConverterBedBlock() { + } + + public int getDataVersion() { + return 1125; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + boolean flag = true; + + try { + net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompoundOrEmpty("Level"); + int i = nbttagcompound1.getIntOr("xPos", 0); + int j = nbttagcompound1.getIntOr("zPos", 0); + ListTag nbttaglist = nbttagcompound1.getListOrEmpty("TileEntities"); + ListTag nbttaglist1 = nbttagcompound1.getListOrEmpty("Sections"); + + for (int k = 0; k < nbttaglist1.size(); ++k) { + net.minecraft.nbt.CompoundTag nbttagcompound2 = nbttaglist1.getCompoundOrEmpty(k); + byte b0 = nbttagcompound2.getByteOr("Y", (byte) 0); + byte[] abyte = nbttagcompound2.getByteArray("Blocks").orElse(new byte[]{}); + + for (int l = 0; l < abyte.length; ++l) { + if (416 == (abyte[l] & 255) << 4) { + int i1 = l & 15; + int j1 = l >> 8 & 15; + int k1 = l >> 4 & 15; + net.minecraft.nbt.CompoundTag nbttagcompound3 = new net.minecraft.nbt.CompoundTag(); + + nbttagcompound3.putString("id", "bed"); + nbttagcompound3.putInt("x", i1 + (i << 4)); + nbttagcompound3.putInt("y", j1 + (b0 << 4)); + nbttagcompound3.putInt("z", k1 + (j << 4)); + nbttaglist.add(nbttagcompound3); + } + } + } + } catch (Exception exception) { + DataConverterBedBlock.a.warn("Unable to datafix Bed blocks, level format may be missing tags."); + } + + return cmp; + } + } + + private static class DataConverterBedItem implements DataConverter { + + DataConverterBedItem() { + } + + public int getDataVersion() { + return 1125; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("minecraft:bed".equals(cmp.getString("id").orElse(null)) && cmp.getShortOr("Damage", (short) 0) == 0) { + cmp.putShort("Damage", (short) DyeColor.RED.getId()); + } + + return cmp; + } + } + + private static class DataConverterSignText implements DataConverter { + + public static final Gson a = new GsonBuilder().registerTypeAdapter(Component.class, new JsonDeserializer() { + MutableComponent a(JsonElement jsonelement, Type type, JsonDeserializationContext jsondeserializationcontext) throws JsonParseException { + if (jsonelement.isJsonPrimitive()) { + return Component.literal(jsonelement.getAsString()); + } else if (jsonelement.isJsonArray()) { + JsonArray jsonarray = jsonelement.getAsJsonArray(); + MutableComponent iTextComponent = null; + Iterator iterator = jsonarray.iterator(); + + while (iterator.hasNext()) { + JsonElement jsonelement1 = iterator.next(); + MutableComponent iTextComponent1 = this.a(jsonelement1, jsonelement1.getClass(), jsondeserializationcontext); + + if (iTextComponent == null) { + iTextComponent = iTextComponent1; + } else { + iTextComponent.append(iTextComponent1); + } + } + + return iTextComponent; + } else { + throw new JsonParseException("Don't know how to turn " + jsonelement + " into a Component"); + } + } + + public Object deserialize(JsonElement jsonelement, Type type, JsonDeserializationContext jsondeserializationcontext) throws JsonParseException { + return this.a(jsonelement, type, jsondeserializationcontext); + } + }).create(); + + DataConverterSignText() { + } + + public int getDataVersion() { + return 101; + } + + public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) { + if ("Sign".equals(cmp.getString("id").orElse(null))) { + this.convert(cmp, "Text1"); + this.convert(cmp, "Text2"); + this.convert(cmp, "Text3"); + this.convert(cmp, "Text4"); + } + + return cmp; + } + + private void convert(net.minecraft.nbt.CompoundTag nbttagcompound, String s) { + String s1 = nbttagcompound.getString(s).orElse(null); + Object object = null; + + if (!"null".equals(s1) && !Strings.isNullOrEmpty(s1)) { + if ((s1.charAt(0) != 34 || s1.charAt(s1.length() - 1) != 34) && (s1.charAt(0) != 123 || s1.charAt(s1.length() - 1) != 125)) { + object = Component.literal(s1); + } else { + try { + object = GsonHelper.fromJson(DataConverterSignText.a, s1, Component.class); + if (object == null) { + object = Component.literal(""); + } + } catch (JsonParseException jsonparseexception) { + ; + } + + if (object == null) { + try { + object = ComponentConverter.Serializer.fromJson(s1, MinecraftServer.getServer().registryAccess()); + } catch (JsonParseException jsonparseexception1) { + ; + } + } + + if (object == null) { + try { + object = ComponentConverter.Serializer.fromJsonLenient(s1, MinecraftServer.getServer().registryAccess()); + } catch (JsonParseException jsonparseexception2) { + ; + } + } + + if (object == null) { + object = Component.literal(s1); + } + } + } else { + object = Component.literal(""); + } + + nbttagcompound.putString(s, ComponentConverter.Serializer.toJson((Component) object, MinecraftServer.getServer().registryAccess())); + } + } + + private static class DataInspectorPlayerVehicle implements DataInspector { + @Override + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + cmp.getCompound("RootVehicle").ifPresent(nbttagcompound1 -> { + if (nbttagcompound1.getCompound("Entity").isPresent()) { + convertCompound(LegacyType.ENTITY, nbttagcompound1, "Entity", sourceVer, targetVer); + } + }); + + return cmp; + } + } + + private static class DataInspectorLevelPlayer implements DataInspector { + @Override + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + if (cmp.getCompound("Player").isPresent()) { + convertCompound(LegacyType.PLAYER, cmp, "Player", sourceVer, targetVer); + } + + return cmp; + } + } + + private static class DataInspectorStructure implements DataInspector { + @Override + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + cmp.getList("entities").ifPresent(nbttaglist -> { + for (int j = 0; j < nbttaglist.size(); ++j) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = (net.minecraft.nbt.CompoundTag) nbttaglist.get(j); + if (nbttagcompound1.getCompound("nbt").isPresent()) { + convertCompound(LegacyType.ENTITY, nbttagcompound1, "nbt", sourceVer, targetVer); + } + } + }); + + cmp.getList("blocks").ifPresent(nbttaglist -> { + for (int j = 0; j < nbttaglist.size(); ++j) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = (net.minecraft.nbt.CompoundTag) nbttaglist.get(j); + if (nbttagcompound1.getCompound("nbt").isPresent()) { + convertCompound(LegacyType.BLOCK_ENTITY, nbttagcompound1, "nbt", sourceVer, targetVer); + } + } + }); + + return cmp; + } + } + + private static class DataInspectorChunks implements DataInspector { + @Override + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + cmp.getCompound("Level").ifPresent(nbttagcompound1 -> { + nbttagcompound1.getList("Entities").ifPresent(nbttaglist -> { + for (int j = 0; j < nbttaglist.size(); ++j) { + nbttaglist.set(j, convert(LegacyType.ENTITY, (net.minecraft.nbt.CompoundTag) nbttaglist.get(j), sourceVer, targetVer)); + } + }); + + nbttagcompound1.getList("TileEntities").ifPresent(nbttaglist -> { + for (int j = 0; j < nbttaglist.size(); ++j) { + nbttaglist.set(j, convert(LegacyType.BLOCK_ENTITY, (net.minecraft.nbt.CompoundTag) nbttaglist.get(j), sourceVer, targetVer)); + } + }); + }); + + return cmp; + } + } + + private static class DataInspectorEntityPassengers implements DataInspector { + @Override + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + cmp.getList("Passengers").ifPresent(nbttaglist -> { + for (int j = 0; j < nbttaglist.size(); ++j) { + nbttaglist.set(j, convert(LegacyType.ENTITY, nbttaglist.getCompoundOrEmpty(j), sourceVer, targetVer)); + } + }); + + return cmp; + } + } + + private static class DataInspectorPlayer implements DataInspector { + @Override + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + convertItems(cmp, "Inventory", sourceVer, targetVer); + convertItems(cmp, "EnderItems", sourceVer, targetVer); + if (cmp.getCompound("ShoulderEntityLeft").isPresent()) { + convertCompound(LegacyType.ENTITY, cmp, "ShoulderEntityLeft", sourceVer, targetVer); + } + + if (cmp.getCompound("ShoulderEntityRight").isPresent()) { + convertCompound(LegacyType.ENTITY, cmp, "ShoulderEntityRight", sourceVer, targetVer); + } + + return cmp; + } + } + + private static class DataInspectorVillagers implements DataInspector { + Identifier entityVillager = getKey("EntityVillager"); + + @Override + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + if (cmp.getString("id").isPresent() && entityVillager.equals(Identifier.parse(cmp.getString("id").get()))) { + cmp.getCompound("Offers").flatMap(nbttagcompound1 -> nbttagcompound1.getList("Recipes")).ifPresent(nbttaglist -> { + for (int j = 0; j < nbttaglist.size(); ++j) { + CompoundTag nbttagcompound2 = nbttaglist.getCompoundOrEmpty(j); + + convertItem(nbttagcompound2, "buy", sourceVer, targetVer); + convertItem(nbttagcompound2, "buyB", sourceVer, targetVer); + convertItem(nbttagcompound2, "sell", sourceVer, targetVer); + nbttaglist.set(j, nbttagcompound2); + } + }); + } + + return cmp; + } + } + + private static class DataInspectorMobSpawnerMinecart implements DataInspector { + Identifier entityMinecartMobSpawner = getKey("EntityMinecartMobSpawner"); + Identifier tileEntityMobSpawner = getKey("TileEntityMobSpawner"); + + @Override + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + String s = cmp.getString("id").get(); + if (entityMinecartMobSpawner.equals(Identifier.parse(s))) { + cmp.putString("id", tileEntityMobSpawner.toString()); + convert(LegacyType.BLOCK_ENTITY, cmp, sourceVer, targetVer); + cmp.putString("id", s); + } + + return cmp; + } + } + + private static class DataInspectorMobSpawnerMobs implements DataInspector { + Identifier tileEntityMobSpawner = getKey("TileEntityMobSpawner"); + + @Override + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + if (cmp.getString("id").isPresent() && tileEntityMobSpawner.equals(Identifier.parse(cmp.getString("id").get()))) { + cmp.getList("SpawnPotentials").ifPresent(nbttaglist -> { + for (int j = 0; j < nbttaglist.size(); ++j) { + net.minecraft.nbt.CompoundTag nbttagcompound1 = nbttaglist.getCompoundOrEmpty(j); + + convertCompound(LegacyType.ENTITY, nbttagcompound1, "Entity", sourceVer, targetVer); + } + }); + + convertCompound(LegacyType.ENTITY, cmp, "SpawnData", sourceVer, targetVer); + } + + return cmp; + } + } + + private static class DataInspectorCommandBlock implements DataInspector { + Identifier tileEntityCommand = getKey("TileEntityCommand"); + + @Override + public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) { + if (cmp.getString("id").isPresent() && tileEntityCommand.equals(Identifier.parse(cmp.getString("id").get()))) { + cmp.putString("id", "Control"); + convert(LegacyType.BLOCK_ENTITY, cmp, sourceVer, targetVer); + cmp.putString("id", "MinecartCommandBlock"); + } + + return cmp; + } + } +} diff --git a/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v26_1/PaperweightFakePlayer.java b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v26_1/PaperweightFakePlayer.java new file mode 100644 index 0000000000..28d048e57a --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v26_1/PaperweightFakePlayer.java @@ -0,0 +1,86 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.bukkit.adapter.impl.v26_1; + +import com.mojang.authlib.GameProfile; +import net.minecraft.server.level.ClientInformation; +import net.minecraft.server.level.ParticleStatus; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.stats.Stat; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.HumanoidArm; +import net.minecraft.world.entity.player.ChatVisiblity; +import net.minecraft.world.level.block.entity.SignBlockEntity; +import net.minecraft.world.phys.Vec3; + +import java.nio.charset.StandardCharsets; +import java.util.OptionalInt; +import java.util.UUID; + +class PaperweightFakePlayer extends ServerPlayer { + private static final GameProfile FAKE_WORLDEDIT_PROFILE = new GameProfile( + UUID.nameUUIDFromBytes("worldedit".getBytes(StandardCharsets.UTF_8)), + "[WorldEdit]" + ); + private static final Vec3 ORIGIN = new Vec3(0.0D, 0.0D, 0.0D); + private static final ClientInformation FAKE_CLIENT_INFO = new ClientInformation( + "en_US", 16, ChatVisiblity.FULL, true, 0, HumanoidArm.LEFT, false, false, ParticleStatus.MINIMAL + ); + + PaperweightFakePlayer(ServerLevel world) { + super(world.getServer(), world, FAKE_WORLDEDIT_PROFILE, FAKE_CLIENT_INFO); + } + + @Override + public Vec3 position() { + return ORIGIN; + } + + @Override + public void tick() { + } + + @Override + public void die(DamageSource damagesource) { + } + + @Override + public OptionalInt openMenu(MenuProvider factory) { + return OptionalInt.empty(); + } + + @Override + public void updateOptions(ClientInformation clientOptions) { + } + + @Override + public void awardStat(Stat stat, int amount) { + } + + @Override + public void awardStat(Stat stat) { + } + + @Override + public void openTextEdit(SignBlockEntity sign, boolean front) { + } +} diff --git a/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v26_1/PaperweightLoggingProblemReporter.java b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v26_1/PaperweightLoggingProblemReporter.java new file mode 100644 index 0000000000..9b5075147f --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v26_1/PaperweightLoggingProblemReporter.java @@ -0,0 +1,61 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.bukkit.adapter.impl.v26_1; + +import net.minecraft.util.ProblemReporter; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.function.Function; +import java.util.function.Supplier; + +final class PaperweightLoggingProblemReporter implements ProblemReporter, AutoCloseable { + private static final Logger LOGGER = LogManager.getLogger(); + + static T with(Supplier contextSupplier, Function consumer) { + try (var problemReporter = new PaperweightLoggingProblemReporter(contextSupplier)) { + return consumer.apply(problemReporter); + } + } + + PaperweightLoggingProblemReporter(Supplier contextSupplier) { + this.contextSupplier = contextSupplier; + } + + private final Collector delegate = new Collector(); + private final Supplier contextSupplier; + + @Override + public ProblemReporter forChild(PathElement child) { + return delegate.forChild(child); + } + + @Override + public void report(Problem problem) { + delegate.report(problem); + } + + @Override + public void close() { + if (!delegate.isEmpty()) { + LOGGER.warn("Problems were reported during {}: {}", contextSupplier.get(), delegate.getTreeReport()); + } + } +} diff --git a/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v26_1/PaperweightServerLevelDelegateProxy.java b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v26_1/PaperweightServerLevelDelegateProxy.java new file mode 100644 index 0000000000..67c848ab3b --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v26_1/PaperweightServerLevelDelegateProxy.java @@ -0,0 +1,314 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.bukkit.adapter.impl.v26_1; + +import com.google.common.collect.ImmutableTable; +import com.google.common.collect.Table; +import com.google.errorprone.annotations.Keep; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.util.concurrency.LazyReference; +import com.sk89q.worldedit.world.entity.EntityTypes; +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.Identifier; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.WorldGenLevel; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.storage.TagValueOutput; +import net.minecraft.world.phys.Vec3; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.enginehub.linbus.tree.LinCompoundTag; +import org.jetbrains.annotations.Nullable; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Predicate; + +public class PaperweightServerLevelDelegateProxy implements InvocationHandler, AutoCloseable { + + private static BlockVector3 adapt(BlockPos blockPos) { + return BlockVector3.at(blockPos.getX(), blockPos.getY(), blockPos.getZ()); + } + + private final EditSession editSession; + private final ServerLevel serverLevel; + private final PaperweightAdapter adapter; + private final Map createdBlockEntities = new HashMap<>(); + + private PaperweightServerLevelDelegateProxy(EditSession editSession, ServerLevel serverLevel, PaperweightAdapter adapter) { + this.editSession = editSession; + this.serverLevel = serverLevel; + this.adapter = adapter; + } + + public record LevelAndProxy(WorldGenLevel level, PaperweightServerLevelDelegateProxy proxy) implements AutoCloseable { + @Override + public void close() throws MaxChangedBlocksException { + proxy.close(); + } + } + + public static LevelAndProxy newInstance(EditSession editSession, ServerLevel serverLevel, PaperweightAdapter adapter) { + PaperweightServerLevelDelegateProxy proxy = new PaperweightServerLevelDelegateProxy(editSession, serverLevel, adapter); + return new LevelAndProxy( + (WorldGenLevel) Proxy.newProxyInstance( + serverLevel.getClass().getClassLoader(), + serverLevel.getClass().getInterfaces(), + proxy + ), + proxy + ); + } + + @Keep + @Nullable + private BlockEntity getBlockEntity(BlockPos blockPos) { + // This doesn't synthesize or load from world. I think editing existing block entities without setting the block + // (in the context of features) should not be supported in the first place. + BlockVector3 pos = adapt(blockPos); + return createdBlockEntities.get(pos); + } + + private BlockState getBlockState(BlockPos blockPos) { + return adapter.adapt(this.editSession.getBlock(blockPos.getX(), blockPos.getY(), blockPos.getZ())); + } + + @Keep + private boolean isStateAtPosition(BlockPos blockPos, Predicate predicate) { + return predicate.test(getBlockState(blockPos)); + } + + private boolean setBlock(BlockPos blockPos, BlockState blockState) { + try { + handleBlockEntity(blockPos, blockState); + return editSession.setBlock(adapt(blockPos), adapter.adapt(blockState)); + } catch (MaxChangedBlocksException e) { + throw new RuntimeException(e); + } + } + + // For BlockEntity#setBlockState, not sure why it's deprecated + @SuppressWarnings("deprecation") + private void handleBlockEntity(BlockPos blockPos, BlockState blockState) { + BlockVector3 pos = adapt(blockPos); + if (blockState.hasBlockEntity()) { + if (!(blockState.getBlock() instanceof EntityBlock entityBlock)) { + // This will probably never happen, as Mojang's own code assumes that + // hasBlockEntity implies instanceof EntityBlock, but just to be safe... + throw new AssertionError("BlockState has block entity but block is not an EntityBlock: " + blockState); + } + BlockEntity newEntity = entityBlock.newBlockEntity(blockPos, blockState); + if (newEntity != null) { + newEntity.setBlockState(blockState); + createdBlockEntities.put(pos, newEntity); + // Should we load existing NBT here? This is for feature / structure gen so it seems unnecessary. + // But it would align with the behavior of the real setBlock method. + return; + } + } + // Discard any block entity that was previously created if new block is set without block entity + createdBlockEntities.remove(pos); + } + + @Keep + private boolean removeBlock(BlockPos blockPos) { + return setBlock(blockPos, Blocks.AIR.defaultBlockState()); + } + + @Keep + private boolean addEntity(Entity entity) { + Vec3 pos = entity.getPosition(0.0f); + Location location = new Location(BukkitAdapter.adapt(serverLevel.getWorld()), pos.x(), pos.y(), pos.z()); + + Identifier id = serverLevel.registryAccess().lookupOrThrow(Registries.ENTITY_TYPE).getKey(entity.getType()); + + net.minecraft.nbt.CompoundTag tag = PaperweightLoggingProblemReporter.with( + () -> "serializing entity " + entity.getStringUUID(), + reporter -> { + var tagValueOutput = TagValueOutput.createWithContext(reporter, serverLevel.registryAccess()); + entity.saveWithoutId(tagValueOutput); + return tagValueOutput.buildResult(); + } + ); + + BaseEntity baseEntity = new BaseEntity(EntityTypes.get(id.toString()), + LazyReference.from(() -> (LinCompoundTag) adapter.toNativeLin(tag))); + + return editSession.createEntity(location, baseEntity) != null; + } + + @Override + public void close() throws MaxChangedBlocksException { + for (Map.Entry entry : createdBlockEntities.entrySet()) { + BlockVector3 blockPos = entry.getKey(); + BlockEntity blockEntity = entry.getValue(); + + net.minecraft.nbt.CompoundTag tag = PaperweightLoggingProblemReporter.with( + () -> "saving block entity at " + blockPos, + reporter -> { + var tagValueOutput = TagValueOutput.createWithContext(reporter, serverLevel.registryAccess()); + blockEntity.saveWithId(tagValueOutput); + return tagValueOutput.buildResult(); + } + ); + editSession.setBlock( + blockPos, + adapter.adapt(blockEntity.getBlockState()) + .toBaseBlock(LazyReference.from(() -> (LinCompoundTag) adapter.toNativeLin(tag))) + ); + } + } + + private static void addMethodHandleToTable( + ImmutableTable.Builder table, + String methodName, + MethodHandle methodHandle + ) { + // We want to call these with Object[] args, not plain args + // We skip the first argument, which is our receiver + MethodHandle spreader = methodHandle.asSpreader( + 1, Object[].class, methodHandle.type().parameterCount() - 1 + ); + // We drop the first argument, which is our receiver + // We also drop the return type, which is not important + table.put(methodName, methodHandle.type().dropParameterTypes(0, 1).changeReturnType(void.class), spreader); + } + + private static final Table METHOD_MAP; + + static { + var lookup = MethodHandles.lookup(); + var builder = ImmutableTable.builder(); + try { + addMethodHandleToTable( + builder, + StaticRefraction.GET_BLOCK_STATE, + lookup.unreflect(PaperweightServerLevelDelegateProxy.class.getDeclaredMethod("getBlockState", BlockPos.class)) + ); + + addMethodHandleToTable( + builder, + StaticRefraction.IS_STATE_AT_POSITION, + lookup.unreflect(PaperweightServerLevelDelegateProxy.class.getDeclaredMethod("isStateAtPosition", BlockPos.class, Predicate.class)) + ); + + MethodHandle addEntity = lookup.unreflect(PaperweightServerLevelDelegateProxy.class.getDeclaredMethod("addEntity", Entity.class)); + addMethodHandleToTable( + builder, + StaticRefraction.ADD_FRESH_ENTITY_WITH_PASSENGERS_ENTITY, + addEntity + ); + addMethodHandleToTable( + builder, + StaticRefraction.ADD_FRESH_ENTITY_WITH_PASSENGERS_ENTITY_SPAWN_REASON, + // 0 - this, 1 - entity, 2 - reason + MethodHandles.dropArguments(addEntity, 2, CreatureSpawnEvent.SpawnReason.class) + ); + addMethodHandleToTable( + builder, + StaticRefraction.ADD_FRESH_ENTITY, + addEntity + ); + addMethodHandleToTable( + builder, + StaticRefraction.ADD_FRESH_ENTITY_SPAWN_REASON, + // 0 - this, 1 - entity, 2 - reason + MethodHandles.dropArguments(addEntity, 2, CreatureSpawnEvent.SpawnReason.class) + ); + + addMethodHandleToTable( + builder, + StaticRefraction.GET_BLOCK_ENTITY, + lookup.unreflect(PaperweightServerLevelDelegateProxy.class.getDeclaredMethod("getBlockEntity", BlockPos.class)) + ); + + MethodHandle setBlock = lookup.unreflect(PaperweightServerLevelDelegateProxy.class.getDeclaredMethod("setBlock", BlockPos.class, BlockState.class)); + addMethodHandleToTable( + builder, + StaticRefraction.SET_BLOCK, + // 0 - this, 1 - blockPos, 2 - blockState, 3 - flags + MethodHandles.dropArguments(setBlock, 3, int.class) + ); + addMethodHandleToTable( + builder, + StaticRefraction.SET_BLOCK_MAX_UPDATE, + // 0 - this, 1 - blockPos, 2 - blockState, 3 - flags, 4 - maxUpdateDepth + MethodHandles.dropArguments(setBlock, 3, int.class, int.class) + ); + + MethodHandle removeBlock = lookup.unreflect(PaperweightServerLevelDelegateProxy.class.getDeclaredMethod("removeBlock", BlockPos.class)); + addMethodHandleToTable( + builder, + StaticRefraction.REMOVE_BLOCK, + // 0 - this, 1 - blockPos, 2 - move + MethodHandles.dropArguments(removeBlock, 2, boolean.class) + ); + addMethodHandleToTable( + builder, + StaticRefraction.DESTROY_BLOCK, + // 0 - this, 1 - blockPos, 2 - drop + MethodHandles.dropArguments(removeBlock, 2, boolean.class) + ); + addMethodHandleToTable( + builder, + StaticRefraction.DESTROY_BLOCK_BREAKING_ENTITY, + // 0 - this, 1 - blockPos, 2 - drop, 3 - breakingEntity + MethodHandles.dropArguments(removeBlock, 2, boolean.class, Entity.class) + ); + addMethodHandleToTable( + builder, + StaticRefraction.DESTROY_BLOCK_BREAKING_ENTITY_MAX_UPDATE, + // 0 - this, 1 - blockPos, 2 - drop, 3 - breakingEntity, 4 - maxUpdateDepth + MethodHandles.dropArguments(removeBlock, 2, boolean.class, Entity.class, int.class) + ); + } catch (IllegalAccessException | NoSuchMethodException e) { + throw new RuntimeException("Failed to bind to own methods", e); + } + METHOD_MAP = builder.build(); + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + MethodHandle delegate = METHOD_MAP.get( + // ignore return type, we only need the parameter types + method.getName(), MethodType.methodType(void.class, method.getParameterTypes()) + ); + if (delegate != null) { + return delegate.invoke(this, args); + } + return method.invoke(this.serverLevel, args); + } + +} diff --git a/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v26_1/PaperweightWorldNativeAccess.java b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v26_1/PaperweightWorldNativeAccess.java new file mode 100644 index 0000000000..e9ad42b903 --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v26_1/PaperweightWorldNativeAccess.java @@ -0,0 +1,193 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.bukkit.adapter.impl.v26_1; + +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.internal.block.BlockStateIdAccess; +import com.sk89q.worldedit.internal.wna.WorldNativeAccess; +import com.sk89q.worldedit.util.SideEffect; +import com.sk89q.worldedit.util.SideEffectSet; +import com.sk89q.worldedit.world.block.BlockState; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.Tag; +import net.minecraft.server.level.FullChunkStatus; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.redstone.ExperimentalRedstoneUtils; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.block.data.CraftBlockData; +import org.bukkit.event.block.BlockPhysicsEvent; +import org.enginehub.linbus.tree.LinCompoundTag; + +import java.lang.ref.WeakReference; +import java.util.Objects; +import javax.annotation.Nullable; + +public class PaperweightWorldNativeAccess implements WorldNativeAccess { + private static final int UPDATE = 1; + private static final int NOTIFY = 2; + + private final PaperweightAdapter adapter; + private final WeakReference world; + private SideEffectSet sideEffectSet; + + public PaperweightWorldNativeAccess(PaperweightAdapter adapter, WeakReference world) { + this.adapter = adapter; + this.world = world; + } + + private ServerLevel getWorld() { + return Objects.requireNonNull(world.get(), "The reference to the world was lost"); + } + + @Override + public void setCurrentSideEffectSet(SideEffectSet sideEffectSet) { + this.sideEffectSet = sideEffectSet; + } + + @Override + public LevelChunk getChunk(int x, int z) { + return getWorld().getChunk(x, z); + } + + @Override + public net.minecraft.world.level.block.state.BlockState toNative(BlockState state) { + int stateId = BlockStateIdAccess.getBlockStateId(state); + return BlockStateIdAccess.isValidInternalId(stateId) + ? Block.stateById(stateId) + : ((CraftBlockData) BukkitAdapter.adapt(state)).getState(); + } + + @Override + public net.minecraft.world.level.block.state.BlockState getBlockState(LevelChunk chunk, BlockPos position) { + return chunk.getBlockState(position); + } + + @Nullable + @Override + public net.minecraft.world.level.block.state.BlockState setBlockState(LevelChunk chunk, BlockPos position, net.minecraft.world.level.block.state.BlockState state) { + return chunk.setBlockState(position, state, this.sideEffectSet.shouldApply(SideEffect.UPDATE) ? 0 : 512); + } + + @Override + public net.minecraft.world.level.block.state.BlockState getValidBlockForPosition(net.minecraft.world.level.block.state.BlockState block, BlockPos position) { + return Block.updateFromNeighbourShapes(block, getWorld(), position); + } + + @Override + public BlockPos getPosition(int x, int y, int z) { + return new BlockPos(x, y, z); + } + + @Override + public void updateLightingForBlock(BlockPos position) { + getWorld().getChunkSource().getLightEngine().checkBlock(position); + } + + @Override + public boolean updateTileEntity(BlockPos position, LinCompoundTag tag) { + // We will assume that the tile entity was created for us + BlockEntity tileEntity = getWorld().getBlockEntity(position); + if (tileEntity == null) { + return false; + } + Tag nativeTag = adapter.fromNative(tag); + PaperweightAdapter.readTagIntoTileEntity((net.minecraft.nbt.CompoundTag) nativeTag, tileEntity); + return true; + } + + @Override + public void notifyBlockUpdate(LevelChunk chunk, BlockPos position, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState) { + if (chunk.getSections()[getWorld().getSectionIndex(position.getY())] != null) { + getWorld().sendBlockUpdated(position, oldState, newState, UPDATE | NOTIFY); + } + } + + @Override + public boolean isChunkTicking(LevelChunk chunk) { + return chunk.getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING); + } + + @Override + public void markBlockChanged(LevelChunk chunk, BlockPos position) { + if (chunk.getSections()[getWorld().getSectionIndex(position.getY())] != null) { + getWorld().getChunkSource().blockChanged(position); + } + } + + @Override + public void notifyNeighbors(BlockPos pos, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState) { + ServerLevel world = getWorld(); + if (sideEffectSet.shouldApply(SideEffect.EVENTS)) { + world.updateNeighborsAt(pos, oldState.getBlock()); + } else { + // When we don't want events, manually run the physics without them. + Block block = oldState.getBlock(); + fireNeighborChanged(pos, world, block, pos.west()); + fireNeighborChanged(pos, world, block, pos.east()); + fireNeighborChanged(pos, world, block, pos.below()); + fireNeighborChanged(pos, world, block, pos.above()); + fireNeighborChanged(pos, world, block, pos.north()); + fireNeighborChanged(pos, world, block, pos.south()); + } + if (newState.hasAnalogOutputSignal()) { + world.updateNeighbourForOutputSignal(pos, newState.getBlock()); + } + } + + @Override + public void updateBlock(BlockPos pos, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState) { + ServerLevel world = getWorld(); + newState.onPlace(world, pos, oldState, false); + } + + private void fireNeighborChanged(BlockPos pos, ServerLevel world, Block block, BlockPos neighborPos) { + world.getBlockState(neighborPos).handleNeighborChanged(world, neighborPos, block, ExperimentalRedstoneUtils.initialOrientation(world, null, null), false); + } + + @Override + public void updateNeighbors(BlockPos pos, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState, int recursionLimit) { + ServerLevel world = getWorld(); + oldState.updateIndirectNeighbourShapes(world, pos, NOTIFY, recursionLimit); + if (sideEffectSet.shouldApply(SideEffect.EVENTS)) { + CraftWorld craftWorld = world.getWorld(); + BlockPhysicsEvent event = new BlockPhysicsEvent(craftWorld.getBlockAt(pos.getX(), pos.getY(), pos.getZ()), newState.asBlockData()); + world.getCraftServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + return; + } + } + newState.updateNeighbourShapes(world, pos, NOTIFY, recursionLimit); + newState.updateIndirectNeighbourShapes(world, pos, NOTIFY, recursionLimit); + } + + @Override + public void onBlockStateChange(BlockPos pos, net.minecraft.world.level.block.state.BlockState oldState, net.minecraft.world.level.block.state.BlockState newState) { + getWorld().updatePOIOnBlockStateChange(pos, oldState, newState); + } + + @Override + public void flush() { + + } + +} diff --git a/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v26_1/StaticRefraction.java b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v26_1/StaticRefraction.java new file mode 100644 index 0000000000..470289f40b --- /dev/null +++ b/worldedit-bukkit/adapters/adapter-26.1/src/main/java/com/sk89q/worldedit/bukkit/adapter/impl/v26_1/StaticRefraction.java @@ -0,0 +1,94 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.bukkit.adapter.impl.v26_1; + +import com.sk89q.worldedit.bukkit.adapter.Refraction; + +/** + * Dedicated class to map all names that we use. + * + *

+ * Overloads are split into multiple fields, as they CAN have different obfuscated names. + *

+ */ +public final class StaticRefraction { + public static final String GET_CHUNK_FUTURE_MAIN_THREAD = Refraction.pickName( + "getChunkFutureMainThread", "c" + ); + public static final String MAIN_THREAD_PROCESSOR = Refraction.pickName( + "mainThreadProcessor", "g" + ); + public static final String NEXT_TICK_TIME = Refraction.pickName("nextTickTimeNanos", "am"); + public static final String GET_BLOCK_STATE = Refraction.pickName("getBlockState", "a_"); + public static final String IS_STATE_AT_POSITION = Refraction.pickName("isStateAtPosition", "a"); + /** + * {@code addFreshEntityWithPassengers(Entity entity)}. + */ + public static final String ADD_FRESH_ENTITY_WITH_PASSENGERS_ENTITY = Refraction.pickName( + "addFreshEntityWithPassengers", "a_" + ); + /** + * {@code addFreshEntityWithPassengers(Entity entity, CreatureSpawnEvent.SpawnReason reason)}. + */ + public static final String ADD_FRESH_ENTITY_WITH_PASSENGERS_ENTITY_SPAWN_REASON = + Refraction.pickName("addFreshEntityWithPassengers", "a_"); + /** + * {@code addFreshEntity(Entity entity)}. + */ + public static final String ADD_FRESH_ENTITY = Refraction.pickName("addFreshEntity", "b"); + /** + * {@code addFreshEntity(Entity entity, CreatureSpawnEvent.SpawnReason reason)}. + */ + public static final String ADD_FRESH_ENTITY_SPAWN_REASON = Refraction.pickName( + "addFreshEntity", "b" + ); + /** + * {@code getBlockEntity(BlockPos blockPos)}. + */ + public static final String GET_BLOCK_ENTITY = Refraction.pickName("getBlockEntity", "c_"); + /** + * {@code setBlock(BlockPos blockPos, BlockState blockState, int flags)}. + */ + public static final String SET_BLOCK = Refraction.pickName("setBlock", "a"); + /** + * {@code setBlock(BlockPos blockPos, BlockState blockState, int flags, int maxUpdateDepth)}. + */ + public static final String SET_BLOCK_MAX_UPDATE = Refraction.pickName("setBlock", "a"); + public static final String REMOVE_BLOCK = Refraction.pickName("removeBlock", "a"); + /** + * {@code destroyBlock(BlockPos blockPos, boolean drop)}. + */ + public static final String DESTROY_BLOCK = Refraction.pickName("destroyBlock", "b"); + /** + * {@code destroyBlock(BlockPos blockPos, boolean drop, Entity breakingEntity)}. + */ + public static final String DESTROY_BLOCK_BREAKING_ENTITY = Refraction.pickName( + "destroyBlock", "a" + ); + /** + * {@code destroyBlock(BlockPos blockPos, boolean drop, Entity breakingEntity, int maxUpdateDepth)}. + */ + public static final String DESTROY_BLOCK_BREAKING_ENTITY_MAX_UPDATE = Refraction.pickName( + "destroyBlock", "a" + ); + + private StaticRefraction() { + } +} diff --git a/worldedit-bukkit/build.gradle.kts b/worldedit-bukkit/build.gradle.kts index c169b8905b..e4d9854c43 100644 --- a/worldedit-bukkit/build.gradle.kts +++ b/worldedit-bukkit/build.gradle.kts @@ -76,7 +76,6 @@ val adaptersReobf = configurations.create("adaptersReobf") { attributes { attribute(Obfuscation.OBFUSCATION_ATTRIBUTE, objects.named(Obfuscation.OBFUSCATED)) } - extendsFrom(adapters) } allprojects { @@ -117,6 +116,9 @@ dependencies { project.project(":worldedit-bukkit:adapters").subprojects.forEach { "adapters"(project(it.path)) + if (it.name.startsWith("adapter-1_")) { + "adaptersReobf"(project(it.path)) + } } compileOnly(libs.worldguard) { exclude("com.sk89q.worldedit", "worldedit-bukkit") diff --git a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/FaweAdapter.java b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/FaweAdapter.java index ffd5500914..92253b146f 100644 --- a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/FaweAdapter.java +++ b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/adapter/FaweAdapter.java @@ -42,6 +42,11 @@ protected FaweAdapter(final BukkitImplAdapter parent) { this.parent = parent; } + @Override + public void initializeRegistries() { + parent.initializeRegistries(); + } + @Override public boolean generateTree( final TreeGenerator.TreeType treeType, diff --git a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/util/MinecraftVersion.java b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/util/MinecraftVersion.java index 15ec1ab71f..ebff1b0043 100644 --- a/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/util/MinecraftVersion.java +++ b/worldedit-bukkit/src/main/java/com/fastasyncworldedit/bukkit/util/MinecraftVersion.java @@ -85,13 +85,23 @@ private static MinecraftVersion detectMinecraftVersion() { minor = Integer.parseInt(parts[1]); release = parts.length == 3 ? Integer.parseInt(parts[2]) : 0; // e.g. 1.18 } else { - String[] parts = getPackageVersion().split("_"); - if (parts.length != 3) { - throw new IllegalStateException("Failed to determine minecraft version!"); + String packageVersion = getPackageVersion(); + String[] parts = packageVersion.split("_"); + if (parts.length == 3 && parts[0].startsWith("v") && parts[2].startsWith("R")) { + major = Integer.parseInt(parts[0].substring(1)); + minor = Integer.parseInt(parts[1]); + release = Integer.parseInt(parts[2].substring(1)); + } else { + // Newer servers may expose unversioned CraftBukkit packages (e.g. "craftbukkit"). + String version = Bukkit.getServer().getBukkitVersion().split("-")[0]; + String[] dotted = version.split(Pattern.quote(".")); + if (dotted.length != 2 && dotted.length != 3) { + throw new IllegalStateException("Failed to determine minecraft version!"); + } + major = Integer.parseInt(dotted[0]); + minor = Integer.parseInt(dotted[1]); + release = dotted.length == 3 ? Integer.parseInt(dotted[2]) : 0; } - major = Integer.parseInt(parts[0].substring(1)); - minor = Integer.parseInt(parts[1]); - release = Integer.parseInt(parts[2].substring(1)); } return new MinecraftVersion(major, minor, release); } @@ -104,7 +114,15 @@ private static MinecraftVersion detectMinecraftVersion() { */ private static String getPackageVersion() { String fullPackagePath = Bukkit.getServer().getClass().getPackage().getName(); - return fullPackagePath.substring(fullPackagePath.lastIndexOf('.') + 1); + if (fullPackagePath.contains(".")) { + String token = fullPackagePath.substring(fullPackagePath.lastIndexOf('.') + 1); + // Keep legacy behavior only for versioned CraftBukkit package names (e.g. v1_21_R7). + if (token.startsWith("v") && token.contains("_R")) { + return token; + } + } + String version = Bukkit.getServer().getBukkitVersion(); + return version.split("-")[0]; } /** diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java index 9ca5f5df6d..3d56ab9168 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitWorld.java @@ -32,6 +32,7 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.blocks.BaseItem; import com.sk89q.worldedit.blocks.BaseItemStack; @@ -332,10 +333,16 @@ public boolean clearContainerBlockContents(BlockVector3 pt) { /** * An EnumMap that stores which WorldEdit TreeTypes apply to which Bukkit TreeTypes. */ + @Deprecated private static final EnumMap treeTypeMapping = new EnumMap<>(TreeGenerator.TreeType.class); static { + generateTreeMap(); + } + + @SuppressWarnings("deprecation") + private static void generateTreeMap() { for (TreeGenerator.TreeType type : TreeGenerator.TreeType.values()) { try { TreeType bukkitType = TreeType.valueOf(type.name()); @@ -365,10 +372,13 @@ public boolean clearContainerBlockContents(BlockVector3 pt) { } } + @Deprecated public static TreeType toBukkitTreeType(TreeGenerator.TreeType type) { return treeTypeMapping.get(type); } + @SuppressWarnings("deprecation") + @Deprecated @Override public boolean generateTree(TreeGenerator.TreeType type, EditSession editSession, BlockVector3 pt) { //FAWE start - allow tree commands to be undone and obey region restrictions @@ -377,6 +387,20 @@ public boolean generateTree(TreeGenerator.TreeType type, EditSession editSession //FAWE end } + @Override + public boolean generateTree( + final com.sk89q.worldedit.world.generation.TreeType type, + final EditSession editSession, + final BlockVector3 position + ) throws MaxChangedBlocksException { + BukkitImplAdapter adapter = WorldEditPlugin.getInstance().getBukkitImplAdapter(); + if (adapter != null) { + return adapter.generateTree(type, getWorld(), editSession, position); + } + // No adapter, we can't generate this. + return false; + } + @Override public void dropItem(Vector3 pt, BaseItemStack item) { World world = getWorld(); diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java index a14b36e859..ea4c09439b 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditPlugin.java @@ -50,6 +50,7 @@ import com.sk89q.worldedit.internal.anvil.ChunkDeleter; import com.sk89q.worldedit.internal.command.CommandUtil; import com.sk89q.worldedit.internal.util.LogManagerCompat; +import com.sk89q.worldedit.registry.Registries; import com.sk89q.worldedit.util.lifecycle.Lifecycled; import com.sk89q.worldedit.util.lifecycle.SimpleLifecycled; import com.sk89q.worldedit.world.World; @@ -260,7 +261,6 @@ public void onEnable() { getServer().getPluginManager().registerEvents(new AsyncTabCompleteListener(), this); } - initializeRegistries(); // this creates the objects matching Bukkit's enums - but doesn't fill them with data yet if (Bukkit.getWorlds().isEmpty()) { setupPreWorldData(); // register this so we can load world-dependent data right as the first world is loading @@ -292,6 +292,7 @@ public void onEnable() { private void setupPreWorldData() { loadAdapter(); + initializeRegistries(); // this creates the objects matching Bukkit's enums - but doesn't fill them with data yet WorldEdit.getInstance().loadMappings(); } @@ -353,9 +354,17 @@ private void initializeRegistries() { EntityType.REGISTRY.register("minecraft:" + lowerCaseMcId, new EntityType("minecraft:" + lowerCaseMcId)); } } + + // Registries only available via NMS + BukkitImplAdapter adapter = getBukkitImplAdapter(); + if (adapter != null) { + adapter.initializeRegistries(); + } + // ... :| GameModes.get(""); WeatherTypes.get(""); + Registries.get(""); } private void setupTags() { diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java index f3db4cb9f3..2fe28534b6 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/adapter/BukkitImplAdapter.java @@ -31,6 +31,7 @@ import com.sk89q.jnbt.LinBusConverter; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.blocks.BaseItem; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.bukkit.BukkitAdapter; @@ -53,6 +54,7 @@ import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; import com.sk89q.worldedit.world.generation.StructureType; +import com.sk89q.worldedit.world.generation.TreeType; import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.registry.BlockMaterial; import org.bukkit.Keyed; @@ -330,6 +332,19 @@ default void sendBiomeUpdates(World world, Iterable chunks) { } + /** + * Generates a Minecraft tree at the given location. + * + * @param treeType The tree + * @param world The world + * @param session The EditSession + * @param pt The location + * @return If it succeeded + */ + default boolean generateTree(TreeType treeType, World world, EditSession session, BlockVector3 pt) throws MaxChangedBlocksException { + throw new UnsupportedOperationException("This adapter does not support generating features."); + } + /** * Generates a Minecraft feature at the given location. * diff --git a/worldedit-cli/src/main/java/com/sk89q/worldedit/cli/schematic/ClipboardWorld.java b/worldedit-cli/src/main/java/com/sk89q/worldedit/cli/schematic/ClipboardWorld.java index 1502fdbf38..e838baee4a 100644 --- a/worldedit-cli/src/main/java/com/sk89q/worldedit/cli/schematic/ClipboardWorld.java +++ b/worldedit-cli/src/main/java/com/sk89q/worldedit/cli/schematic/ClipboardWorld.java @@ -41,13 +41,13 @@ import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.util.SideEffect; import com.sk89q.worldedit.util.SideEffectSet; -import com.sk89q.worldedit.util.TreeGenerator; import com.sk89q.worldedit.world.AbstractWorld; import com.sk89q.worldedit.world.RegenOptions; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.world.block.BlockStateHolder; +import com.sk89q.worldedit.world.generation.TreeType; import javax.annotation.Nullable; import java.io.File; @@ -141,8 +141,7 @@ public boolean regenerate(Region region, Extent extent, RegenOptions options) { } @Override - public boolean generateTree(TreeGenerator.TreeType type, EditSession editSession, BlockVector3 position) - throws MaxChangedBlocksException { + public boolean generateTree(TreeType type, EditSession editSession, BlockVector3 position) throws MaxChangedBlocksException { return false; } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/Fawe.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/Fawe.java index 57a61d1f0e..37c6796b5a 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/Fawe.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/Fawe.java @@ -43,6 +43,7 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; /** * [ WorldEdit action ] @@ -413,26 +414,31 @@ private void setupMemoryListener() { final MemoryMXBean memBean = ManagementFactory.getMemoryMXBean(); final NotificationEmitter ne = (NotificationEmitter) memBean; + AtomicLong lastWarn = new AtomicLong(System.currentTimeMillis()); ne.addNotificationListener((notification, handback) -> { final long heapSize = Runtime.getRuntime().totalMemory(); final long heapMaxSize = Runtime.getRuntime().maxMemory(); if (heapSize < heapMaxSize) { return; } - LOGGER.warn("High memory usage detected, FAWE will attempt to slow operations to prevent a crash."); + final long time = System.currentTimeMillis(); + if (time > lastWarn.get() + TimeUnit.SECONDS.toMillis(30)) { + lastWarn.set(time); + LOGGER.warn("High memory usage detected, FAWE will attempt to slow operations to prevent a crash."); + } MemUtil.memoryLimitedTask(); }, null, null); final List memPools = ManagementFactory.getMemoryPoolMXBeans(); for (final MemoryPoolMXBean mp : memPools) { - if (mp.isUsageThresholdSupported()) { + if (mp.isCollectionUsageThresholdSupported()) { final MemoryUsage mu = mp.getUsage(); final long max = mu.getMax(); if (max < 0) { continue; } final long alert = (max * Settings.settings().MAX_MEMORY_PERCENT) / 100; - mp.setUsageThreshold(alert); + mp.setCollectionUsageThreshold(alert); } } } catch (Throwable ignored) { diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/PlacementStateProcessor.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/PlacementStateProcessor.java index 165a1b3159..3028c5f3ce 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/PlacementStateProcessor.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/processor/PlacementStateProcessor.java @@ -13,6 +13,7 @@ import com.fastasyncworldedit.core.queue.IChunkGet; import com.fastasyncworldedit.core.queue.IChunkSet; import com.fastasyncworldedit.core.registry.state.PropertyKey; +import com.fastasyncworldedit.core.util.ExtentTraverser; import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.extent.AbstractDelegateExtent; import com.sk89q.worldedit.extent.Extent; @@ -24,6 +25,7 @@ import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.registry.state.Property; import com.sk89q.worldedit.util.Direction; +import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockCategories; import com.sk89q.worldedit.world.block.BlockCategory; @@ -114,6 +116,18 @@ protected PlacementStateProcessor( this.finished = finished; } + protected static World getWorldFromExtent(Extent extent) throws UnsupportedOperationException { + World world = ExtentTraverser.getWorldFromExtent(extent); + if (world == null) { + throw new UnsupportedOperationException( + "World is required for PlacementStateProcessor but none found in given extent (" + extent + .getClass() + .getName() + ")."); + } + return world; + + } + private static void setup() { NullExtent nullExtent = new NullExtent( com.sk89q.worldedit.extent.NullExtent.INSTANCE, diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/ExtentTraverser.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/ExtentTraverser.java index 9b584fa5df..a436a2a3c9 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/ExtentTraverser.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/util/ExtentTraverser.java @@ -12,6 +12,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.lang.reflect.Field; +import java.util.Optional; public class ExtentTraverser { @@ -45,7 +46,8 @@ public static World getWorldFromExtent(Extent extent) { } else if (extent instanceof ParallelQueueExtent pqe) { return ((SingleThreadQueueExtent) pqe.getExtent()).getWorld(); } else { - return new ExtentTraverser<>(extent).findAndGet(World.class); + return Optional.ofNullable(new ExtentTraverser<>(extent).findAndGet(World.class)).orElseGet(() -> Optional.ofNullable( + new ExtentTraverser<>(extent).findAndGet(EditSession.class)).map(EditSession::getWorld).orElse(null)); } } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/wrappers/WorldWrapper.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/wrappers/WorldWrapper.java index 7fd2344095..211cd85c0c 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/wrappers/WorldWrapper.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/wrappers/WorldWrapper.java @@ -40,6 +40,7 @@ import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; import com.sk89q.worldedit.world.generation.StructureType; +import com.sk89q.worldedit.world.generation.TreeType; import com.sk89q.worldedit.world.weather.WeatherType; import javax.annotation.Nullable; @@ -294,6 +295,12 @@ public boolean generateTree(TreeGenerator.TreeType type, EditSession editSession } } + @Override + public boolean generateTree(final TreeType type, final EditSession editSession, final BlockVector3 position) throws + MaxChangedBlocksException { + return parent.generateTree(type, editSession, position); + } + @Override public boolean generateStructure(final StructureType type, final EditSession editSession, final BlockVector3 position) { return parent.generateStructure(type, editSession, position); diff --git a/worldedit-core/src/main/java/com/sk89q/util/StringUtil.java b/worldedit-core/src/main/java/com/sk89q/util/StringUtil.java index 7c50cd9cb8..1a30098f42 100644 --- a/worldedit-core/src/main/java/com/sk89q/util/StringUtil.java +++ b/worldedit-core/src/main/java/com/sk89q/util/StringUtil.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.regex.Pattern; /** * String utilities. @@ -337,6 +338,58 @@ public static List parseListInQuotes( return parsableBlocks; } + public static List parseListInQuotes(String[] input, char delimiter, char[] quoteOpen, char[] quoteClose, boolean appendLeftover) { + List parsableBlocks = new ArrayList<>(); + StringBuilder buffer = new StringBuilder(); + int quotes = quoteOpen.length; + if (quotes != quoteClose.length) { + throw new Error("Mismatched quoteOpen and quoteClose lengths"); + } + for (String split : input) { + boolean quoteHandled = false; + for (int i = 0; i < quotes; i++) { + if (split.indexOf(quoteOpen[i]) != -1 && split.indexOf(quoteClose[i]) == -1) { + buffer.append(split).append(delimiter); + quoteHandled = true; + break; + } else if (split.indexOf(quoteClose[i]) != -1 && split.indexOf(quoteOpen[i]) == -1) { + buffer.append(split); + parsableBlocks.add(buffer.toString()); + buffer = new StringBuilder(); + quoteHandled = true; + break; + } + } + if (!quoteHandled) { + if (buffer.length() == 0) { + parsableBlocks.add(split); + } else { + buffer.append(split).append(delimiter); + } + } + } + if (appendLeftover && buffer.length() != 0) { + parsableBlocks.add(buffer.delete(buffer.length() - 1, buffer.length()).toString()); + } + + return parsableBlocks; + } + + /** + * Converts a glob pattern to a regex pattern, supporting * and ?. + * + *

+ * Note: this assumes that the text has been pre-validated or quoted, to not contain any regex special characters. + *

+ * + * @param glob The glob pattern + * @return The regex pattern + */ + public static Pattern convertGlobToRegex(String glob) { + return Pattern.compile("^" + glob + .replace("*", ".*") + .replace("?", ".") + "$"); + } //FAWE start /** diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java index 29c3e26032..330d2b6678 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/EditSession.java @@ -150,6 +150,7 @@ import com.sk89q.worldedit.world.block.BlockTypes; import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; import com.sk89q.worldedit.world.generation.StructureType; +import com.sk89q.worldedit.world.generation.TreeType; import com.sk89q.worldedit.world.registry.LegacyMapper; import org.apache.logging.log4j.Logger; @@ -3033,7 +3034,9 @@ public int makePumpkinPatches(BlockVector3 position, int apothem, double density * @param treeType the tree type * @return number of trees created * @throws MaxChangedBlocksException thrown if too many blocks are changed + * @deprecated Use {@link #makeForest(Region, double, TreeType)}. */ + @Deprecated public int makeForest(BlockVector3 basePosition, int size, double density, TreeGenerator.TreeType treeType) throws MaxChangedBlocksException { return makeForest(CuboidRegion.fromCenter(basePosition, size), density, treeType); @@ -3047,7 +3050,9 @@ public int makeForest(BlockVector3 basePosition, int size, double density, TreeG * @param treeType the tree type * @return number of trees created * @throws MaxChangedBlocksException thrown if too many blocks are changed + * @deprecated Use {@link #makeForest(Region, double, TreeType)}. */ + @Deprecated public int makeForest(Region region, double density, TreeGenerator.TreeType treeType) throws MaxChangedBlocksException { ForestGenerator generator = new ForestGenerator(this, treeType); GroundFunction ground = new GroundFunction(new ExistingBlockMask(this), generator); @@ -3059,6 +3064,38 @@ public int makeForest(Region region, double density, TreeGenerator.TreeType tree return ground.getAffected(); } + /** + * Makes a forest. + * + * @param basePosition a position + * @param size a size + * @param density between 0 and 1, inclusive + * @param treeType the tree type + * @return number of trees created + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + public int makeForest(BlockVector3 basePosition, int size, double density, TreeType treeType) throws MaxChangedBlocksException { + return makeForest(CuboidRegion.fromCenter(basePosition, size), density, treeType); + } + + /** + * Makes a forest. + * + * @param region the region to generate trees in + * @param density between 0 and 1, inclusive + * @param treeType the tree type + * @return number of trees created + * @throws MaxChangedBlocksException thrown if too many blocks are changed + */ + public int makeForest(Region region, double density, TreeType treeType) throws MaxChangedBlocksException { + com.sk89q.worldedit.function.generator.TreeGenerator generator = new com.sk89q.worldedit.function.generator.TreeGenerator(this, treeType); + GroundFunction ground = new GroundFunction(new ExistingBlockMask(this), generator); + LayerVisitor visitor = new LayerVisitor(asFlatRegion(region), minimumBlockY(region), maximumBlockY(region), ground); + visitor.setMask(new NoiseFilter2D(new RandomNoise(), density)); + Operations.completeLegacy(visitor); + return ground.getAffected(); + } + /** * Get the block distribution inside a region. * diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/ApplyBrushCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/ApplyBrushCommands.java index 66e801ae2d..ba5f095a84 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/ApplyBrushCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/ApplyBrushCommands.java @@ -40,8 +40,8 @@ import com.sk89q.worldedit.internal.command.CommandRegistrationHandler; import com.sk89q.worldedit.internal.expression.Expression; import com.sk89q.worldedit.regions.factory.RegionFactory; -import com.sk89q.worldedit.util.TreeGenerator; import com.sk89q.worldedit.util.formatting.text.TextComponent; +import com.sk89q.worldedit.world.generation.TreeType; import org.enginehub.piston.CommandManager; import org.enginehub.piston.CommandManagerService; import org.enginehub.piston.CommandParameters; @@ -115,7 +115,7 @@ public void forest( CommandParameters parameters, Player player, LocalSession localSession, @Arg(desc = "The type of tree to plant") - TreeGenerator.TreeType type + TreeType type ) throws WorldEditException { setApplyBrush(parameters, player, localSession, new TreeGeneratorFactory(type)); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java index e1a2e3e401..1042932e9b 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java @@ -117,7 +117,6 @@ import com.sk89q.worldedit.regions.factory.SphereRegionFactory; import com.sk89q.worldedit.session.ClipboardHolder; import com.sk89q.worldedit.util.HandSide; -import com.sk89q.worldedit.util.TreeGenerator; import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; @@ -128,6 +127,7 @@ import com.sk89q.worldedit.world.block.BlockTypes; import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; import com.sk89q.worldedit.world.generation.StructureType; +import com.sk89q.worldedit.world.generation.TreeType; import org.anarres.parallelgzip.ParallelGZIPOutputStream; import org.enginehub.piston.annotation.Command; import org.enginehub.piston.annotation.CommandContainer; @@ -1189,7 +1189,7 @@ public void forest( @Arg(desc = "The density of the brush", def = "20") double density, @Arg(desc = "The type of tree to use") - TreeGenerator.TreeType type + TreeType type ) throws WorldEditException { setOperationBasedBrush(player, localSession, radius, new Paint(new TreeGeneratorFactory(type), density / 100), shape, "worldedit.brush.forest" diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/GeneralCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/GeneralCommands.java index 865e6f937b..7e7a22de3c 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/GeneralCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/GeneralCommands.java @@ -29,6 +29,7 @@ import com.fastasyncworldedit.core.util.StringMan; import com.fastasyncworldedit.core.util.TextureUtil; import com.google.common.collect.ImmutableList; +import com.sk89q.util.StringUtil; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.LocalConfiguration; import com.sk89q.worldedit.LocalSession; @@ -49,6 +50,11 @@ import com.sk89q.worldedit.internal.command.CommandRegistrationHandler; import com.sk89q.worldedit.internal.command.CommandUtil; import com.sk89q.worldedit.internal.cui.ServerCUIHandler; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.registry.Keyed; +import com.sk89q.worldedit.registry.Registry; +import com.sk89q.worldedit.session.Placement; +import com.sk89q.worldedit.session.PlacementType; import com.sk89q.worldedit.util.SideEffect; import com.sk89q.worldedit.util.SideEffectSet; import com.sk89q.worldedit.util.auth.AuthorizationException; @@ -57,7 +63,12 @@ import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; +import com.sk89q.worldedit.util.formatting.text.event.ClickEvent; +import com.sk89q.worldedit.util.formatting.text.event.HoverEvent; +import com.sk89q.worldedit.util.formatting.text.format.TextColor; import com.sk89q.worldedit.world.World; +import com.sk89q.worldedit.world.biome.BiomeType; +import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.item.ItemType; import org.enginehub.piston.CommandManager; import org.enginehub.piston.CommandManagerService; @@ -76,6 +87,7 @@ import java.util.Set; import java.util.TreeMap; import java.util.concurrent.Callable; +import java.util.regex.Pattern; import java.util.stream.Collectors; import static com.google.common.base.Preconditions.checkNotNull; @@ -658,4 +670,76 @@ public Component call() throws Exception { } //FAWE end + + private static final Pattern ALLOWED_KEY_CHARACTERS = Pattern.compile("[a-z0-9_:?*/]+"); + + @Command( + name = "/registry", + desc = "Search through the given registry" + ) + @CommandPermissions("worldedit.registry") + public void registry(Actor actor, + @Arg(desc = "The registry to search through") + Registry registry, + @ArgFlag(name = 'p', desc = "Page of results to return", def = "1") + int page, + @Arg(desc = "Search query", variable = true, def = "*") + List queryBits) { + String query = String.join("_", queryBits); + + if (!ALLOWED_KEY_CHARACTERS.matcher(query).matches()) { + actor.printError(TranslatableComponent.of("worldedit.registry.error.invalid-key")); + } + + WorldEditAsyncCommandBuilder.createAndSendMessage(actor, new RegistrySearcher(registry, query, page), + TranslatableComponent.of("worldedit.registry.searching", TextComponent.of(query))); + } + + private static class RegistrySearcher implements Callable { + private final Registry registry; + private final String search; + private final int page; + private final Pattern matcher; + + RegistrySearcher(Registry registry, String search, int page) { + this.registry = registry; + this.search = search; + this.page = page; + + String matcherQuery = search; + if (!matcherQuery.contains("*") && !matcherQuery.contains("?")) { + // If there are no wildcards, add them around the query + matcherQuery = "*" + matcherQuery + "*"; + } + + this.matcher = StringUtil.convertGlobToRegex(matcherQuery); + } + + @Override + public Component call() throws Exception { + String command = "//registry " + registry.id() + " -p %page% " + search; + Map results = new TreeMap<>(); + for (Keyed searchType : registry) { + final String id = searchType.id(); + if (matcher.matcher(id).matches()) { + var builder = TextComponent.builder() + .append(searchType.id()) + .clickEvent(ClickEvent.copyToClipboard(searchType.id())); + switch (searchType) { + case ItemType itemType -> builder.hoverEvent(HoverEvent.showText(itemType.getRichName())); + case BlockType blockType -> builder.hoverEvent(HoverEvent.showText(blockType.getRichName())); + case BiomeType biomeType -> builder.hoverEvent(HoverEvent.showText(biomeType.getRichName())); + default -> { + } + } + results.put(id, builder.build()); + } + } + List list = new ArrayList<>(results.values()); + boolean isBlank = search.isBlank() || search.equals("*"); + String title = isBlank ? "Registry contents" : "Search results for '" + search + "'"; + return PaginationBox.fromComponents(title, command, list) + .create(page); + } + } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java index 3206524baf..d949859837 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java @@ -53,12 +53,12 @@ import com.sk89q.worldedit.math.Vector3; import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.Region; -import com.sk89q.worldedit.util.TreeGenerator.TreeType; import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; import com.sk89q.worldedit.world.generation.StructureType; +import com.sk89q.worldedit.world.generation.TreeType; import org.enginehub.piston.annotation.Command; import org.enginehub.piston.annotation.CommandContainer; import org.enginehub.piston.annotation.param.Arg; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/PaintBrushCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/PaintBrushCommands.java index c34c66055a..f2f276cde0 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/PaintBrushCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/PaintBrushCommands.java @@ -39,9 +39,9 @@ import com.sk89q.worldedit.internal.annotation.Direction; import com.sk89q.worldedit.internal.command.CommandRegistrationHandler; import com.sk89q.worldedit.regions.factory.RegionFactory; -import com.sk89q.worldedit.util.TreeGenerator; import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.util.formatting.text.TranslatableComponent; +import com.sk89q.worldedit.world.generation.TreeType; import org.enginehub.piston.CommandManager; import org.enginehub.piston.CommandManagerService; import org.enginehub.piston.CommandParameters; @@ -130,7 +130,7 @@ public void forest( CommandParameters parameters, Player player, LocalSession localSession, @Arg(desc = "The type of tree to plant") - TreeGenerator.TreeType type + TreeType type ) throws WorldEditException { setPaintBrush(parameters, player, localSession, new TreeGeneratorFactory(type)); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java index 197968bb36..57432cae58 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java @@ -68,13 +68,13 @@ import com.sk89q.worldedit.regions.RegionOperationException; import com.sk89q.worldedit.regions.Regions; import com.sk89q.worldedit.util.Location; -import com.sk89q.worldedit.util.TreeGenerator.TreeType; import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.world.RegenOptions; import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockTypes; +import com.sk89q.worldedit.world.generation.TreeType; import org.enginehub.piston.annotation.Command; import org.enginehub.piston.annotation.CommandContainer; import org.enginehub.piston.annotation.param.Arg; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java index 67e1e33d98..eabef192a0 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java @@ -24,6 +24,7 @@ import com.fastasyncworldedit.core.event.extent.ActorSaveClipboardEvent; import com.fastasyncworldedit.core.extent.clipboard.MultiClipboardHolder; import com.fastasyncworldedit.core.extent.clipboard.URIClipboardHolder; +import com.fastasyncworldedit.core.internal.exception.FaweException; import com.fastasyncworldedit.core.math.transform.MutatingOperationTransformHolder; import com.fastasyncworldedit.core.util.MainUtil; import com.google.common.collect.Multimap; @@ -576,15 +577,20 @@ public void save( ClipboardHolder holder = session.getClipboard(); SchematicSaveTask task = new SchematicSaveTask(actor, f, dir, format, holder, overwrite); - AsyncCommandBuilder.wrap(task, actor) - .registerWithSupervisor(worldEdit.getSupervisor(), "Saving schematic " + filename) - .setDelayMessage(Caption.of("worldedit.schematic.save.saving")) - .onSuccess(filename + " saved" + (overwrite ? " (overwriting previous file)." : "."), null) - .onFailure( - Caption.of("worldedit.schematic.failed-to-save"), - worldEdit.getPlatformManager().getPlatformCommandManager().getExceptionConverter() - ) - .buildAndExec(worldEdit.getExecutorService()); + AsyncCommandBuilder + .wrap(task, actor) + .registerWithSupervisor(worldEdit.getSupervisor(), "Saving schematic " + filename) + .setDelayMessage(Caption.of("worldedit.schematic.save.saving")) + .onSuccess( + overwrite + ? Caption.of("fawe.worldedit.schematic.schematic.overwritten") + : Caption.of("fawe.worldedit.schematic.schematic.saved", filename), null + ) + .onFailure( + Caption.of("worldedit.schematic.failed-to-save"), + worldEdit.getPlatformManager().getPlatformCommandManager().getExceptionConverter() + ) + .buildAndExec(worldEdit.getExecutorService()); } @Command( @@ -1001,14 +1007,8 @@ public Void call() throws Exception { int limit = actor.getLimit().SCHEM_FILE_NUM_LIMIT; if (numFiles >= limit) { - TextComponent noSlotsErr = TextComponent.of( //TODO - to be moved into captions/translatablecomponents - String.format( - "You have " + numFiles + "/" + limit + " saved schematics. Delete some to save this one!", - TextColor.RED - )); LOGGER.info(actor.getName() + " failed to save " + file.getCanonicalPath() + " - too many schematics!"); - throw new WorldEditException(noSlotsErr) { - }; + throw new FaweException(Caption.of("fawe.error.schematic.over.limit", numFiles, limit)); } } //FAWE end @@ -1034,9 +1034,7 @@ public Void call() throws Exception { closer.close(); // release the new .schem file so that its size can be measured double filesizeKb = Files.size(Paths.get(file.getAbsolutePath())) / 1000.0; - TextComponent filesizeNotif = TextComponent.of( //TODO - to be moved into captions/translatablecomponents - SCHEMATIC_NAME + " size: " + String.format("%.1f", filesizeKb) + "kb", TextColor.GRAY); - actor.print(filesizeNotif); + actor.print(Caption.of("fawe.worldedit.schematic.schematic.size", SCHEMATIC_NAME, String.format("%.1f", filesizeKb))); if (checkFilesize) { @@ -1049,18 +1047,12 @@ public Void call() throws Exception { if ((curKb) > allocatedKb) { file.delete(); - TextComponent notEnoughKbErr = TextComponent.of( - //TODO - to be moved into captions/translatablecomponents - "You're about to be at " + String.format("%.1f", curKb) + "kb of schematics. (" - + String.format( - "%dkb", - allocatedKb - ) + " available) Delete some first to save this one!", - TextColor.RED - ); LOGGER.info(actor.getName() + " failed to save " + SCHEMATIC_NAME + " - not enough space!"); - throw new WorldEditException(notEnoughKbErr) { - }; + throw new FaweException(Caption.of( + "fawe.error.schematic.over.disk.limit", + String.format("%.1f", curKb), + String.format("%dkb", allocatedKb) + )); } if (overwrite) { new File(curFilepath).delete(); @@ -1068,23 +1060,12 @@ public Void call() throws Exception { } else { numFiles++; } - TextComponent kbRemainingNotif = TextComponent.of( - //TODO - to be moved into captions/translatablecomponents - "You have " + String.format("%.1f", (allocatedKb - curKb)) + "kb left for schematics.", - TextColor.GRAY - ); - actor.print(kbRemainingNotif); + + actor.print(Caption.of("fawe.worldedit.schematic.schematic.disk.space", String.format("%.1f", (allocatedKb - curKb)))); } if (Settings.settings().PATHS.PER_PLAYER_SCHEMATICS && actor.getLimit().SCHEM_FILE_NUM_LIMIT > -1) { - - TextComponent slotsRemainingNotif = TextComponent.of( - //TODO - to be moved into captions/translatablecomponents - "You have " + (actor.getLimit().SCHEM_FILE_NUM_LIMIT - numFiles) - + " schematic file slots left.", - TextColor.GRAY - ); - actor.print(slotsRemainingNotif); + actor.print(Caption.of("fawe.worldedit.schematic.schematic.slots.free", (actor.getLimit().SCHEM_FILE_NUM_LIMIT - numFiles))); } LOGGER.info(actor.getName() + " saved " + file.getCanonicalPath()); } else { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/ToolCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/ToolCommands.java index 1e63b947e5..83e712e3e1 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/ToolCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/ToolCommands.java @@ -51,7 +51,6 @@ import com.sk89q.worldedit.internal.command.CommandRegistrationHandler; import com.sk89q.worldedit.internal.command.CommandUtil; import com.sk89q.worldedit.util.HandSide; -import com.sk89q.worldedit.util.TreeGenerator; import com.sk89q.worldedit.util.formatting.text.Component; import com.sk89q.worldedit.util.formatting.text.TextComponent; import com.sk89q.worldedit.util.formatting.text.event.ClickEvent; @@ -59,6 +58,7 @@ import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; import com.sk89q.worldedit.world.generation.StructureType; +import com.sk89q.worldedit.world.generation.TreeType; import org.enginehub.piston.CommandManager; import org.enginehub.piston.CommandManagerService; import org.enginehub.piston.CommandMetadata; @@ -242,7 +242,7 @@ public void inspectBrush(Player player, LocalSession session) throws WorldEditEx public void tree( Player player, LocalSession session, @Arg(desc = "Type of tree to generate", def = "tree") - TreeGenerator.TreeType type + TreeType type ) throws WorldEditException { setTool(player, session, new TreePlanter(type), "worldedit.tool.tree.equip"); } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/RegistryConverter.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/RegistryConverter.java index c9b69cc30c..371e3db93d 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/RegistryConverter.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/argument/RegistryConverter.java @@ -20,6 +20,7 @@ package com.sk89q.worldedit.command.argument; import com.google.common.collect.ImmutableList; +import com.google.common.reflect.TypeToken; import com.sk89q.worldedit.command.util.SuggestionHelper; import com.sk89q.worldedit.registry.Keyed; import com.sk89q.worldedit.registry.Registry; @@ -34,6 +35,7 @@ import com.sk89q.worldedit.world.gamemode.GameMode; import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; import com.sk89q.worldedit.world.generation.StructureType; +import com.sk89q.worldedit.world.generation.TreeType; import com.sk89q.worldedit.world.item.ItemCategory; import com.sk89q.worldedit.world.item.ItemType; import com.sk89q.worldedit.world.weather.WeatherType; @@ -48,7 +50,6 @@ import java.lang.reflect.Field; import java.util.List; import java.util.Locale; -import java.util.stream.Collectors; public final class RegistryConverter implements ArgumentConverter { @@ -66,13 +67,17 @@ public static void register(CommandManager commandManager) { GameMode.class, WeatherType.class, ConfiguredFeatureType.class, - StructureType.class + StructureType.class, + TreeType.class ) .stream() .map(c -> (Class) c) .forEach(registryType -> commandManager.registerConverter(Key.of(registryType), from(registryType)) ); + + // This must be separate as it has a generic type + commandManager.registerConverter(Key.of(new TypeToken<>() {}), new RegistryConverter<>(Registry.REGISTRY)); } @SuppressWarnings("unchecked") @@ -112,7 +117,7 @@ public ConversionResult convert(String argument, InjectedValueAccess injected @Override public List getSuggestions(String input, InjectedValueAccess context) { - return SuggestionHelper.getRegistrySuggestions(registry, input).collect(Collectors.toList()); + return SuggestionHelper.getRegistrySuggestions(registry, input).toList(); } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/factory/TreeGeneratorFactory.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/factory/TreeGeneratorFactory.java index 1e6b06d7bc..421b5c675e 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/factory/TreeGeneratorFactory.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/factory/TreeGeneratorFactory.java @@ -22,20 +22,20 @@ import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.function.Contextual; import com.sk89q.worldedit.function.EditContext; -import com.sk89q.worldedit.function.generator.ForestGenerator; -import com.sk89q.worldedit.util.TreeGenerator; +import com.sk89q.worldedit.function.generator.TreeGenerator; +import com.sk89q.worldedit.world.generation.TreeType; -public final class TreeGeneratorFactory implements Contextual { +public final class TreeGeneratorFactory implements Contextual { - private final TreeGenerator.TreeType type; + private final TreeType type; - public TreeGeneratorFactory(TreeGenerator.TreeType type) { + public TreeGeneratorFactory(TreeType type) { this.type = type; } @Override - public ForestGenerator createFromContext(EditContext input) { - return new ForestGenerator((EditSession) input.getDestination(), type); + public TreeGenerator createFromContext(EditContext input) { + return new TreeGenerator((EditSession) input.getDestination(), type); } @Override diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/TreePlanter.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/TreePlanter.java index db92f6af91..e5740dcb5a 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/TreePlanter.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/tool/TreePlanter.java @@ -30,7 +30,7 @@ import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.util.Direction; import com.sk89q.worldedit.util.Location; -import com.sk89q.worldedit.util.TreeGenerator; +import com.sk89q.worldedit.world.generation.TreeType; import javax.annotation.Nullable; @@ -39,9 +39,9 @@ */ public class TreePlanter implements BlockTool { - private final TreeGenerator.TreeType treeType; + private final TreeType treeType; - public TreePlanter(TreeGenerator.TreeType treeType) { + public TreePlanter(TreeType treeType) { this.treeType = treeType; } @@ -66,7 +66,7 @@ public boolean actPrimary( final BlockVector3 pos = clicked.toVector().add(0, 1, 0).toBlockPoint(); for (int i = 0; i < 10; i++) { - if (treeType.generate(editSession, pos)) { + if (player.getWorld().generateTree(treeType, editSession, pos)) { successful = true; break; } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/generator/ForestGenerator.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/generator/ForestGenerator.java index a042fd16c1..72b763cd18 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/function/generator/ForestGenerator.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/generator/ForestGenerator.java @@ -31,7 +31,10 @@ /** * Generates forests by searching for the ground starting from the given upper Y * coordinate for every column given. + * + * @deprecated Use {@link com.sk89q.worldedit.function.generator.TreeGenerator} instead. */ +@Deprecated public class ForestGenerator implements RegionFunction { private final TreeGenerator.TreeType treeType; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/function/generator/TreeGenerator.java b/worldedit-core/src/main/java/com/sk89q/worldedit/function/generator/TreeGenerator.java new file mode 100644 index 0000000000..35809a9187 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/function/generator/TreeGenerator.java @@ -0,0 +1,48 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.function.generator; + +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.function.RegionFunction; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.world.generation.TreeType; + +public final class TreeGenerator implements RegionFunction { + + private final TreeType treeType; + private final EditSession editSession; + + /** + * Create a new instance. + * + * @param editSession the edit session + * @param treeType the tree type + */ + public TreeGenerator(EditSession editSession, TreeType treeType) { + this.editSession = editSession; + this.treeType = treeType; + } + + @Override + public boolean apply(BlockVector3 position) throws WorldEditException { + return editSession.getWorld().generateTree(treeType, editSession, position); + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/Constants.java b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/Constants.java index cd053a2895..551d14d6d4 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/internal/Constants.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/internal/Constants.java @@ -138,4 +138,14 @@ private Constants() { * The DataVersion for Minecraft 1.21.11 */ public static final int DATA_VERSION_MC_1_21_11 = 4671; + + /** + * The DataVersion for Minecraft 26.1. + */ + public static final int DATA_VERSION_MC_26_1 = 4786; + + /** + * The DataVersion for Minecraft 26.1.1. + */ + public static final int DATA_VERSION_MC_26_1_1 = 4788; } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/registry/NamespacedRegistry.java b/worldedit-core/src/main/java/com/sk89q/worldedit/registry/NamespacedRegistry.java index e6715907c3..5954154365 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/registry/NamespacedRegistry.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/registry/NamespacedRegistry.java @@ -43,23 +43,36 @@ public final class NamespacedRegistry extends Registry { private int lastInternalId = 0; //FAWE end + @Deprecated public NamespacedRegistry(final String name) { this(name, MINECRAFT_NAMESPACE); } + @Deprecated public NamespacedRegistry(final String name, final boolean checkInitialized) { this(name, MINECRAFT_NAMESPACE, checkInitialized); } + @Deprecated public NamespacedRegistry(final String name, final String defaultNamespace) { this(name, defaultNamespace, false); } + @Deprecated public NamespacedRegistry(final String name, final String defaultNamespace, final boolean checkInitialized) { super(name, checkInitialized); this.defaultNamespace = defaultNamespace; } + public NamespacedRegistry(final String name, final String id, final String defaultNamespace) { + this(name, id, defaultNamespace, false); + } + + public NamespacedRegistry(final String name, final String id, final String defaultNamespace, final boolean checkInitialized) { + super(name, id, checkInitialized); + this.defaultNamespace = defaultNamespace; + } + @Nullable @Override public V get(final String key) { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/registry/Registries.java b/worldedit-core/src/main/java/com/sk89q/worldedit/registry/Registries.java new file mode 100644 index 0000000000..7e3321e5fe --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/registry/Registries.java @@ -0,0 +1,62 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.sk89q.worldedit.registry; + +import com.sk89q.worldedit.world.biome.BiomeCategory; +import com.sk89q.worldedit.world.biome.BiomeType; +import com.sk89q.worldedit.world.block.BlockCategory; +import com.sk89q.worldedit.world.block.BlockType; +import com.sk89q.worldedit.world.entity.EntityType; +import com.sk89q.worldedit.world.fluid.FluidCategory; +import com.sk89q.worldedit.world.fluid.FluidType; +import com.sk89q.worldedit.world.gamemode.GameMode; +import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; +import com.sk89q.worldedit.world.generation.StructureType; +import com.sk89q.worldedit.world.item.ItemCategory; +import com.sk89q.worldedit.world.item.ItemType; +import com.sk89q.worldedit.world.weather.WeatherType; + +import javax.annotation.Nullable; + +public class Registries { + public static final Registry BLOCK_TYPE = addRegistry(BlockType.REGISTRY); + public static final Registry BLOCK_CATEGORY = addRegistry(BlockCategory.REGISTRY); + public static final Registry ITEM_TYPE = addRegistry(ItemType.REGISTRY); + public static final Registry ITEM_CATEGORY = addRegistry(ItemCategory.REGISTRY); + public static final Registry GAME_MODE = addRegistry(GameMode.REGISTRY); + public static final Registry WEATHER_TYPE = addRegistry(WeatherType.REGISTRY); + public static final Registry BIOME_TYPE = addRegistry(BiomeType.REGISTRY); + public static final Registry BIOME_CATEGORY = addRegistry(BiomeCategory.REGISTRY); + public static final Registry ENTITY_TYPE = addRegistry(EntityType.REGISTRY); + public static final Registry FLUID_TYPE = addRegistry(FluidType.REGISTRY); + public static final Registry FLUID_CATEGORY = addRegistry(FluidCategory.REGISTRY); + public static final Registry CONFIGURED_FEATURE_TYPE = addRegistry(ConfiguredFeatureType.REGISTRY); + public static final Registry STRUCTURE_TYPE = addRegistry(StructureType.REGISTRY); + + private static Registry addRegistry(Registry registry) { + Registry.REGISTRY.register(registry.id(), registry); + return registry; + } + + @Nullable + public static Registry get(final String id) { + return Registry.REGISTRY.get(id); + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/registry/Registry.java b/worldedit-core/src/main/java/com/sk89q/worldedit/registry/Registry.java index b29af49e8a..f3d0c958c8 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/registry/Registry.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/registry/Registry.java @@ -33,18 +33,60 @@ import static com.google.common.base.Preconditions.checkState; import static java.util.Objects.requireNonNull; -public class Registry implements Iterable { - +public class Registry implements Iterable, Keyed { + public static final Registry> REGISTRY = new Registry<>("registry", "registry"); private final Map map = new HashMap<>(); private final String name; + private final String id; private final boolean checkInitialized; + private static String nameToId(String name) { + return name.toLowerCase(Locale.ROOT).replace(' ', '_'); + } + + /** + * Creates a new Registry. + * + * @param name The name of the registry + * @deprecated Use {@link #Registry(String, String)} instead to provide an ID + */ + @Deprecated public Registry(final String name) { this(name, false); } + /** + * Creates a new Registry. + * + * @param name The name of the registry + * @param checkInitialized Whether to check if WorldEdit is initialized + * @deprecated Use {@link #Registry(String, String, boolean)} instead to provide an ID + */ + @Deprecated public Registry(final String name, final boolean checkInitialized) { + this(name, nameToId(name), checkInitialized); + } + + /** + * Creates a new Registry. + * + * @param name The name of the registry + * @param id The ID of the registry + */ + public Registry(final String name, final String id) { + this(name, id, false); + } + + /** + * Creates a new Registry. + * + * @param name The name of the registry + * @param id The ID of the registry + * @param checkInitialized Whether to check if WorldEdit is initialized + */ + public Registry(final String name, final String id, final boolean checkInitialized) { this.name = name; + this.id = id; this.checkInitialized = checkInitialized; } @@ -52,6 +94,11 @@ public String getName() { return name; } + @Override + public String id() { + return this.id; + } + //FAWE start public Map getMap() { return map; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/util/TreeGenerator.java b/worldedit-core/src/main/java/com/sk89q/worldedit/util/TreeGenerator.java index 2b6bae6b73..68ce6f4c1e 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/util/TreeGenerator.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/util/TreeGenerator.java @@ -41,8 +41,10 @@ /** * Tree generator. */ +@Deprecated public final class TreeGenerator { + @Deprecated public enum TreeType { TREE("Oak tree", "oak", "tree", "regular"), BIG_TREE("Large oak tree", "largeoak", "bigoak", "big", "bigtree"), diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/NullWorld.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/NullWorld.java index d3ad7bfd92..3832f50a29 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/NullWorld.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/NullWorld.java @@ -38,7 +38,6 @@ import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.util.SideEffect; import com.sk89q.worldedit.util.SideEffectSet; -import com.sk89q.worldedit.util.TreeGenerator.TreeType; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.biome.BiomeTypes; import com.sk89q.worldedit.world.block.BaseBlock; @@ -143,7 +142,7 @@ public boolean regenerate(Region region, EditSession editSession) { } @Override - public boolean generateTree(TreeType type, EditSession editSession, BlockVector3 position) throws MaxChangedBlocksException { + public boolean generateTree(com.sk89q.worldedit.world.generation.TreeType type, EditSession editSession, BlockVector3 position) throws MaxChangedBlocksException { return false; } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/World.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/World.java index 8c9efb5bda..2430e705f8 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/World.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/World.java @@ -48,6 +48,7 @@ import com.sk89q.worldedit.world.block.BlockType; import com.sk89q.worldedit.world.generation.ConfiguredFeatureType; import com.sk89q.worldedit.world.generation.StructureType; +import com.sk89q.worldedit.world.generation.TreeType; import com.sk89q.worldedit.world.weather.WeatherType; import javax.annotation.Nullable; @@ -309,9 +310,24 @@ default boolean regenerate(Region region, Extent extent, RegenOptions options) { * @param position the position * @return true if generation was successful * @throws MaxChangedBlocksException thrown if too many blocks were changed + * @deprecated Use {@link #generateTree(TreeType, EditSession, BlockVector3)} instead */ - boolean generateTree(TreeGenerator.TreeType type, EditSession editSession, BlockVector3 position) throws - MaxChangedBlocksException; + @Deprecated + default boolean generateTree(TreeGenerator.TreeType type, EditSession editSession, BlockVector3 position) throws + MaxChangedBlocksException { + return false; + } + + /** + * Generate a tree at the given position. + * + * @param type the tree type + * @param editSession the {@link EditSession} + * @param position the position + * @return true if generation was successful + * @throws MaxChangedBlocksException thrown if too many blocks were changed + */ + boolean generateTree(TreeType type, EditSession editSession, BlockVector3 position) throws MaxChangedBlocksException; /** * Generate a structure at the given position diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/biome/BiomeCategory.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/biome/BiomeCategory.java index a51a868237..725ebcad7b 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/biome/BiomeCategory.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/biome/BiomeCategory.java @@ -31,7 +31,7 @@ */ public class BiomeCategory extends Category implements Keyed { - public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("biome tag", true); + public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("biome tag", "biome_tag", "minecraft", true); public BiomeCategory(final String id) { super(id); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/biome/BiomeType.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/biome/BiomeType.java index d49289093b..e187f57fbe 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/biome/BiomeType.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/biome/BiomeType.java @@ -20,10 +20,13 @@ package com.sk89q.worldedit.world.biome; import com.fastasyncworldedit.core.registry.RegistryItem; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.extension.platform.Capability; import com.sk89q.worldedit.function.pattern.BiomePattern; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.registry.Keyed; import com.sk89q.worldedit.registry.NamespacedRegistry; +import com.sk89q.worldedit.util.formatting.text.Component; /** * All the types of biomes in the game. @@ -32,7 +35,7 @@ public class BiomeType implements RegistryItem, Keyed, BiomePattern { //FAWE end - public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("biome type", true); + public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("biome type", "biome_type", "minecraft", true); //FAWE start private final String id; @@ -105,4 +108,8 @@ public BiomeType applyBiome(BlockVector3 position) { return this; } + public Component getRichName() { + return WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.GAME_HOOKS) + .getRegistries().getBiomeRegistry().getRichName(this); + } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockCategories.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockCategories.java index 370c537dd6..4161f8da03 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockCategories.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockCategories.java @@ -40,7 +40,7 @@ public final class BlockCategories { public static final BlockCategory BADLANDS_TERRACOTTA = get("minecraft:badlands_terracotta"); public static final BlockCategory AZALEA_ROOT_REPLACEABLE = get("minecraft:azalea_root_replaceable"); public static final BlockCategory BAMBOO_BLOCKS = get("minecraft:bamboo_blocks"); - public static final BlockCategory BAMBOO_PLANTABLE_ON = get("minecraft:bamboo_plantable_on"); + @Deprecated public static final BlockCategory BAMBOO_PLANTABLE_ON = get("minecraft:bamboo_plantable_on"); public static final BlockCategory BANNERS = get("minecraft:banners"); public static final BlockCategory BARS = get("minecraft:bars"); public static final BlockCategory BASE_STONE_NETHER = get("minecraft:base_stone_nether"); @@ -51,7 +51,9 @@ public final class BlockCategories { public static final BlockCategory BEE_ATTRACTIVE = get("minecraft:bee_attractive"); public static final BlockCategory BEE_GROWABLES = get("minecraft:bee_growables"); public static final BlockCategory BEEHIVES = get("minecraft:beehives"); - public static final BlockCategory BIG_DRIPLEAF_PLACEABLE = get("minecraft:big_dripleaf_placeable"); + public static final BlockCategory BENEATH_BAMBOO_PODZOL_REPLACEABLE = get("minecraft:beneath_bamboo_podzol_replaceable"); + public static final BlockCategory BENEATH_TREE_PODZOL_REPLACEABLE = get("minecraft:beneath_tree_podzol_replaceable"); + @Deprecated public static final BlockCategory BIG_DRIPLEAF_PLACEABLE = get("minecraft:big_dripleaf_placeable"); public static final BlockCategory BIRCH_LOGS = get("minecraft:birch_logs"); public static final BlockCategory BLOCKS_WIND_CHARGE_EXPLOSIONS = get("minecraft:blocks_wind_charge_explosions"); public static final BlockCategory BUTTONS = get("minecraft:buttons"); @@ -61,6 +63,10 @@ public final class BlockCategories { public static final BlockCategory CAN_GLIDE_THROUGH = get("minecraft:can_glide_through"); public static final BlockCategory CANDLE_CAKES = get("minecraft:candle_cakes"); public static final BlockCategory CANDLES = get("minecraft:candles"); + public static final BlockCategory CANNOT_REPLACE_BELOW_TREE_TRUNK = get("minecraft:cannot_replace_below_tree_trunk"); + public static final BlockCategory CANNOT_SUPPORT_KELP = get("minecraft:cannot_support_kelp"); + public static final BlockCategory CANNOT_SUPPORT_SEAGRASS = get("minecraft:cannot_support_seagrass"); + public static final BlockCategory CANNOT_SUPPORT_SNOW_LAYER = get("minecraft:cannot_support_snow_layer"); @Deprecated public static final BlockCategory CARPETS = get("minecraft:carpets"); public static final BlockCategory CAULDRONS = get("minecraft:cauldrons"); public static final BlockCategory CAVE_VINES = get("minecraft:cave_vines"); @@ -96,9 +102,11 @@ public final class BlockCategories { public static final BlockCategory DRAGON_IMMUNE = get("minecraft:dragon_immune"); public static final BlockCategory DRAGON_TRANSPARENT = get("minecraft:dragon_transparent"); public static final BlockCategory DRIPSTONE_REPLACEABLE_BLOCKS = get("minecraft:dripstone_replaceable_blocks"); - public static final BlockCategory DRY_VEGETATION_MAY_PLACE_ON = get("minecraft:dry_vegetation_may_place_on"); + @Deprecated public static final BlockCategory DRY_VEGETATION_MAY_PLACE_ON = get("minecraft:dry_vegetation_may_place_on"); public static final BlockCategory EDIBLE_FOR_SHEEP = get("minecraft:edible_for_sheep"); public static final BlockCategory EMERALD_ORES = get("minecraft:emerald_ores"); + public static final BlockCategory ENABLES_BUBBLE_COLUMN_DRAG_DOWN = get("minecraft:enables_bubble_column_drag_down"); + public static final BlockCategory ENABLES_BUBBLE_COLUMN_PUSH_UP = get("minecraft:enables_bubble_column_push_up"); public static final BlockCategory ENCHANTMENT_POWER_PROVIDER = get("minecraft:enchantment_power_provider"); public static final BlockCategory ENCHANTMENT_POWER_TRANSMITTER = get("minecraft:enchantment_power_transmitter"); public static final BlockCategory ENDERMAN_HOLDABLE = get("minecraft:enderman_holdable"); @@ -109,15 +117,21 @@ public final class BlockCategories { public static final BlockCategory FIRE = get("minecraft:fire"); public static final BlockCategory FLOWER_POTS = get("minecraft:flower_pots"); public static final BlockCategory FLOWERS = get("minecraft:flowers"); + public static final BlockCategory FOREST_ROCK_CAN_PLACE_ON = get("minecraft:forest_rock_can_place_on"); public static final BlockCategory FOXES_SPAWNABLE_ON = get("minecraft:foxes_spawnable_on"); public static final BlockCategory FROG_PREFER_JUMP_TO = get("minecraft:frog_prefer_jump_to"); public static final BlockCategory FROGS_SPAWNABLE_ON = get("minecraft:frogs_spawnable_on"); public static final BlockCategory GOATS_SPAWNABLE_ON = get("minecraft:goats_spawnable_on"); public static final BlockCategory GEODE_INVALID_BLOCKS = get("minecraft:geode_invalid_blocks"); public static final BlockCategory GOLD_ORES = get("minecraft:gold_ores"); + public static final BlockCategory GRASS_BLOCKS = get("minecraft:grass_blocks"); + public static final BlockCategory GROWS_CROPS = get("minecraft:grows_crops"); public static final BlockCategory GUARDED_BY_PIGLINS = get("minecraft:guarded_by_piglins"); public static final BlockCategory HOGLIN_REPELLENTS = get("minecraft:hoglin_repellents"); + public static final BlockCategory HUGE_BROWN_MUSHROOM_CAN_PLACE_ON = get("minecraft:huge_brown_mushroom_can_place_on"); + public static final BlockCategory HUGE_RED_MUSHROOM_CAN_PLACE_ON = get("minecraft:huge_red_mushroom_can_place_on"); public static final BlockCategory ICE = get("minecraft:ice"); + public static final BlockCategory ICE_SPIKE_REPLACEABLE = get("minecraft:ice_spike_replaceable"); public static final BlockCategory IMPERMEABLE = get("minecraft:impermeable"); public static final BlockCategory INCORRECT_FOR_COPPER_TOOL = get("minecraft:incorrect_for_copper_tool"); public static final BlockCategory INCORRECT_FOR_DIAMOND_TOOL = get("minecraft:incorrect_for_diamond_tool"); @@ -152,8 +166,10 @@ public final class BlockCategories { public static final BlockCategory MINEABLE_SHOVEL = get("minecraft:mineable/shovel"); public static final BlockCategory MOB_INTERACTABLE_DOORS = get("minecraft:mob_interactable_doors"); public static final BlockCategory MOOSHROOMS_SPAWNABLE_ON = get("minecraft:mooshrooms_spawnable_on"); + public static final BlockCategory MOSS_BLOCKS = get("minecraft:moss_blocks"); public static final BlockCategory MOSS_REPLACEABLE = get("minecraft:moss_replaceable"); - public static final BlockCategory MUSHROOM_GROW_BLOCK = get("minecraft:mushroom_grow_block"); + public static final BlockCategory MUD = get("minecraft:mud"); + @Deprecated public static final BlockCategory MUSHROOM_GROW_BLOCK = get("minecraft:mushroom_grow_block"); public static final BlockCategory NEEDS_DIAMOND_TOOL = get("minecraft:needs_diamond_tool"); public static final BlockCategory NEEDS_IRON_TOOL = get("minecraft:needs_iron_tool"); public static final BlockCategory NEEDS_STONE_TOOL = get("minecraft:needs_stone_tool"); @@ -161,6 +177,7 @@ public final class BlockCategories { public static final BlockCategory NYLIUM = get("minecraft:nylium"); public static final BlockCategory OAK_LOGS = get("minecraft:oak_logs"); public static final BlockCategory OCCLUDES_VIBRATION_SIGNALS = get("minecraft:occludes_vibration_signals"); + public static final BlockCategory OVERRIDES_MUSHROOM_LIGHT_REQUIREMENT = get("minecraft:overrides_mushroom_light_requirement"); public static final BlockCategory OVERWORLD_CARVER_REPLACEABLES = get("minecraft:overworld_carver_replaceables"); public static final BlockCategory OVERWORLD_NATURAL_LOGS = get("minecraft:overworld_natural_logs"); public static final BlockCategory PALE_OAK_LOGS = get("minecraft:pale_oak_logs"); @@ -172,6 +189,7 @@ public final class BlockCategories { public static final BlockCategory PORTALS = get("minecraft:portals"); public static final BlockCategory PRESSURE_PLATES = get("minecraft:pressure_plates"); public static final BlockCategory PREVENT_MOB_SPAWNING_INSIDE = get("minecraft:prevent_mob_spawning_inside"); + public static final BlockCategory PREVENTS_NEARBY_LEAF_DECAY = get("minecraft:prevents_nearby_leaf_decay"); public static final BlockCategory RABBITS_SPAWNABLE_ON = get("minecraft:rabbits_spawnable_on"); public static final BlockCategory RAILS = get("minecraft:rails"); public static final BlockCategory REDSTONE_ORES = get("minecraft:redstone_ores"); @@ -186,15 +204,15 @@ public final class BlockCategories { public static final BlockCategory SHULKER_BOXES = get("minecraft:shulker_boxes"); public static final BlockCategory SIGNS = get("minecraft:signs"); public static final BlockCategory SLABS = get("minecraft:slabs"); - public static final BlockCategory SMALL_DRIPLEAF_PLACEABLE = get("minecraft:small_dripleaf_placeable"); + @Deprecated public static final BlockCategory SMALL_DRIPLEAF_PLACEABLE = get("minecraft:small_dripleaf_placeable"); public static final BlockCategory SMALL_FLOWERS = get("minecraft:small_flowers"); public static final BlockCategory SMELTS_TO_GLASS = get("minecraft:smelts_to_glass"); public static final BlockCategory SNAPS_GOAT_HORN = get("minecraft:snaps_goat_horn"); public static final BlockCategory SNIFFER_DIGGABLE_BLOCK = get("minecraft:sniffer_diggable_block"); public static final BlockCategory SNIFFER_EGG_HATCH_BOOST = get("minecraft:sniffer_egg_hatch_boost"); public static final BlockCategory SNOW = get("minecraft:snow"); - public static final BlockCategory SNOW_LAYER_CAN_SURVIVE_ON = get("minecraft:snow_layer_can_survive_on"); - public static final BlockCategory SNOW_LAYER_CANNOT_SURVIVE_ON = get("minecraft:snow_layer_cannot_survive_on"); + @Deprecated public static final BlockCategory SNOW_LAYER_CAN_SURVIVE_ON = get("minecraft:snow_layer_can_survive_on"); + @Deprecated public static final BlockCategory SNOW_LAYER_CANNOT_SURVIVE_ON = get("minecraft:snow_layer_cannot_survive_on"); public static final BlockCategory SOUL_FIRE_BASE_BLOCKS = get("minecraft:soul_fire_base_blocks"); public static final BlockCategory SOUL_SPEED_BLOCKS = get("minecraft:soul_speed_blocks"); public static final BlockCategory SPRUCE_LOGS = get("minecraft:spruce_logs"); @@ -205,6 +223,39 @@ public final class BlockCategories { public static final BlockCategory STONE_ORE_REPLACEABLES = get("minecraft:stone_ore_replaceables"); public static final BlockCategory STONE_PRESSURE_PLATES = get("minecraft:stone_pressure_plates"); public static final BlockCategory STRIDER_WARM_BLOCKS = get("minecraft:strider_warm_blocks"); + public static final BlockCategory SUBSTRATE_OVERWORLD = get("minecraft:substrate_overworld"); + public static final BlockCategory SUPPORT_OVERRIDE_CACTUS_FLOWER = get("minecraft:support_override_cactus_flower"); + public static final BlockCategory SUPPORT_OVERRIDE_SNOW_LAYER = get("minecraft:support_override_snow_layer"); + public static final BlockCategory SUPPORTS_AZALEA = get("minecraft:supports_azalea"); + public static final BlockCategory SUPPORTS_BAMBOO = get("minecraft:supports_bamboo"); + public static final BlockCategory SUPPORTS_BIG_DRIPLEAF = get("minecraft:supports_big_dripleaf"); + public static final BlockCategory SUPPORTS_CACTUS = get("minecraft:supports_cactus"); + public static final BlockCategory SUPPORTS_CHORUS_FLOWER = get("minecraft:supports_chorus_flower"); + public static final BlockCategory SUPPORTS_CHORUS_PLANT = get("minecraft:supports_chorus_plant"); + public static final BlockCategory SUPPORTS_COCOA = get("minecraft:supports_cocoa"); + public static final BlockCategory SUPPORTS_CRIMSON_FUNGUS = get("minecraft:supports_crimson_fungus"); + public static final BlockCategory SUPPORTS_CRIMSON_ROOTS = get("minecraft:supports_crimson_roots"); + public static final BlockCategory SUPPORTS_CROPS = get("minecraft:supports_crops"); + public static final BlockCategory SUPPORTS_DRY_VEGETATION = get("minecraft:supports_dry_vegetation"); + public static final BlockCategory SUPPORTS_FROGSPAWN = get("minecraft:supports_frogspawn"); + public static final BlockCategory SUPPORTS_HANGING_MANGROVE_PROPAGULE = get("minecraft:supports_hanging_mangrove_propagule"); + public static final BlockCategory SUPPORTS_LILY_PAD = get("minecraft:supports_lily_pad"); + public static final BlockCategory SUPPORTS_MANGROVE_PROPAGULE = get("minecraft:supports_mangrove_propagule"); + public static final BlockCategory SUPPORTS_MELON_STEM = get("minecraft:supports_melon_stem"); + public static final BlockCategory SUPPORTS_MELON_STEM_FRUIT = get("minecraft:supports_melon_stem_fruit"); + public static final BlockCategory SUPPORTS_NETHER_SPROUTS = get("minecraft:supports_nether_sprouts"); + public static final BlockCategory SUPPORTS_NETHER_WART = get("minecraft:supports_nether_wart"); + public static final BlockCategory SUPPORTS_PUMPKIN_STEM = get("minecraft:supports_pumpkin_stem"); + public static final BlockCategory SUPPORTS_PUMPKIN_STEM_FRUIT = get("minecraft:supports_pumpkin_stem_fruit"); + public static final BlockCategory SUPPORTS_SMALL_DRIPLEAF = get("minecraft:supports_small_dripleaf"); + public static final BlockCategory SUPPORTS_STEM_CROPS = get("minecraft:supports_stem_crops"); + public static final BlockCategory SUPPORTS_STEM_FRUIT = get("minecraft:supports_stem_fruit"); + public static final BlockCategory SUPPORTS_SUGAR_CANE = get("minecraft:supports_sugar_cane"); + public static final BlockCategory SUPPORTS_SUGAR_CANE_ADJACENTLY = get("minecraft:supports_sugar_cane_adjacently"); + public static final BlockCategory SUPPORTS_VEGETATION = get("minecraft:supports_vegetation"); + public static final BlockCategory SUPPORTS_WARPED_FUNGUS = get("minecraft:supports_warped_fungus"); + public static final BlockCategory SUPPORTS_WARPED_ROOTS = get("minecraft:supports_warped_roots"); + public static final BlockCategory SUPPORTS_WITHER_ROSE = get("minecraft:supports_wither_rose"); public static final BlockCategory SWORD_EFFICIENT = get("minecraft:sword_efficient"); public static final BlockCategory SWORD_INSTANTLY_MINES = get("minecraft:sword_instantly_mines"); @Deprecated public static final BlockCategory TALL_FLOWERS = get("minecraft:tall_flowers"); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockCategory.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockCategory.java index 29d8ee53bc..8e809a8ec9 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockCategory.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockCategory.java @@ -36,7 +36,7 @@ public class BlockCategory extends Category implements Keyed { //FAWE start private boolean[] flatMap; //FAWE end - public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("block tag", true); + public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("block tag", "block_tag", "minecraft", true); public BlockCategory(final String id) { super(id); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockType.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockType.java index 4592c7ce2c..bb5af10e03 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockType.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockType.java @@ -56,7 +56,7 @@ public class BlockType implements Keyed, Pattern { //FAWE end - public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("block type", true); + public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("block type", "block_type", "minecraft", true); private static final Logger LOGGER = LogManagerCompat.getLogger(); private final String id; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockTypes.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockTypes.java index 9d59912235..dab3732634 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockTypes.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockTypes.java @@ -878,6 +878,8 @@ public final class BlockTypes { @Nullable public static final BlockType GOLD_ORE = init(); @Nullable + public static final BlockType GOLDEN_DANDELION = init(); + @Nullable public static final BlockType GRANITE = init(); @Nullable public static final BlockType GRANITE_SLAB = init(); @@ -1625,6 +1627,8 @@ public final class BlockTypes { @Nullable public static final BlockType POTTED_FLOWERING_AZALEA = init(); @Nullable + public static final BlockType POTTED_GOLDEN_DANDELION = init(); + @Nullable public static final BlockType POTTED_JUNGLE_SAPLING = init(); @Nullable public static final BlockType POTTED_LILY_OF_THE_VALLEY = init(); @@ -2396,7 +2400,7 @@ public final class BlockTypes { public static final BlockType ZOMBIE_WALL_HEAD = init(); private static Field[] fieldsTmp; - private static int initIndex = 0; + private static int initIndex; // Init each field // The order is important @@ -2411,6 +2415,10 @@ public static BlockType init() { // Clears memory after initialization static { + // we should be at the first non-BlockType field now + if (!fieldsTmp[initIndex].getName().equals("fieldsTmp")) { + throw new IllegalStateException("improper initialization of block type fields"); + } fieldsTmp = null; } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/entity/EntityType.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/entity/EntityType.java index aba23b6361..75f2023ed5 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/entity/EntityType.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/entity/EntityType.java @@ -27,7 +27,7 @@ public class EntityType implements RegistryItem, Keyed { //FAWE end - public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("entity type", true); + public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("entity type", "entity_type", "minecraft", true); //FAWE start private final String id; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/fluid/FluidCategory.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/fluid/FluidCategory.java index 775ec58a68..7bcb24717b 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/fluid/FluidCategory.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/fluid/FluidCategory.java @@ -32,7 +32,7 @@ */ public class FluidCategory extends Category implements Keyed { - public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("fluid tag"); + public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("fluid tag", "fluid_tag", "minecraft"); public FluidCategory(final String id) { super(id); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/fluid/FluidType.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/fluid/FluidType.java index 1aca4c25f7..5be794849d 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/fluid/FluidType.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/fluid/FluidType.java @@ -30,7 +30,7 @@ public class FluidType implements RegistryItem, Keyed { //FAWE end - public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("fluid type"); + public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("fluid type", "fluid_type", "minecraft"); //FAWE start private final String id; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/gamemode/GameMode.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/gamemode/GameMode.java index 94b7b7cfe6..075820cc7d 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/gamemode/GameMode.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/gamemode/GameMode.java @@ -24,7 +24,7 @@ public record GameMode(String id) implements Keyed { - public static final Registry REGISTRY = new Registry<>("game mode"); + public static final Registry REGISTRY = new Registry<>("game mode", "game_mode"); /** * Gets the name of this game mode, or the ID if the name cannot be found. diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/generation/ConfiguredFeatureType.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/generation/ConfiguredFeatureType.java index 378fe484d7..f99aa951b1 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/generation/ConfiguredFeatureType.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/generation/ConfiguredFeatureType.java @@ -31,7 +31,7 @@ public record ConfiguredFeatureType(String id, boolean place_on_face) implements Keyed { //FAWE end - public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("configured feature type"); + public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("configured feature type", "configured_feature_type", "minecraft"); //FAWE start - place on face public ConfiguredFeatureType(String id) { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/generation/StructureType.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/generation/StructureType.java index 06fef05f8d..3f42847992 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/generation/StructureType.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/generation/StructureType.java @@ -26,7 +26,7 @@ public record StructureType(String id) implements Keyed { - public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("structure type"); + public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("structure type", "structure_type", "minecraft"); @Override public String toString() { diff --git a/worldedit-bukkit/src/test/java/com/sk89q/worldedit/bukkit/BukkitWorldTest.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/generation/TreeType.java similarity index 65% rename from worldedit-bukkit/src/test/java/com/sk89q/worldedit/bukkit/BukkitWorldTest.java rename to worldedit-core/src/main/java/com/sk89q/worldedit/world/generation/TreeType.java index df6b33b7b7..27dfd646c6 100644 --- a/worldedit-bukkit/src/test/java/com/sk89q/worldedit/bukkit/BukkitWorldTest.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/generation/TreeType.java @@ -17,19 +17,17 @@ * along with this program. If not, see . */ -package com.sk89q.worldedit.bukkit; +package com.sk89q.worldedit.world.generation; -import com.sk89q.worldedit.util.TreeGenerator; -import org.junit.jupiter.api.Test; +import com.sk89q.worldedit.registry.Keyed; +import com.sk89q.worldedit.registry.NamespacedRegistry; -import static org.junit.jupiter.api.Assertions.assertNotNull; +public record TreeType(String id) implements Keyed { -public class BukkitWorldTest { + public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("tree_type", "minecraft"); - public void testTreeTypeMapping() { - for (TreeGenerator.TreeType type : TreeGenerator.TreeType.values()) { - assertNotNull(BukkitWorld.toBukkitTreeType(type), "No mapping for: " + type); - } + @Override + public String toString() { + return this.id; } - } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/item/ItemCategories.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/item/ItemCategories.java index 8fb8decda5..21c5e97ef1 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/item/ItemCategories.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/item/ItemCategories.java @@ -52,7 +52,9 @@ public final class ItemCategories { public static final ItemCategory CAMEL_HUSK_FOOD = get("minecraft:camel_husk_food"); public static final ItemCategory CANDLES = get("minecraft:candles"); @Deprecated public static final ItemCategory CARPETS = get("minecraft:carpets"); + public static final ItemCategory CAT_COLLAR_DYES = get("minecraft:cat_collar_dyes"); public static final ItemCategory CAT_FOOD = get("minecraft:cat_food"); + public static final ItemCategory CAULDRON_CAN_REMOVE_DYE = get("minecraft:cauldron_can_remove_dye"); public static final ItemCategory CHAINS = get("minecraft:chains"); public static final ItemCategory CHERRY_LOGS = get("minecraft:cherry_logs"); public static final ItemCategory CHEST_ARMOR = get("minecraft:chest_armor"); @@ -82,7 +84,8 @@ public final class ItemCategories { public static final ItemCategory DOORS = get("minecraft:doors"); public static final ItemCategory DROWNED_PREFERRED_WEAPONS = get("minecraft:drowned_preferred_weapons"); public static final ItemCategory DUPLICATES_ALLAYS = get("minecraft:duplicates_allays"); - public static final ItemCategory DYEABLE = get("minecraft:dyeable"); + @Deprecated public static final ItemCategory DYEABLE = get("minecraft:dyeable"); + public static final ItemCategory DYES = get("minecraft:dyes"); public static final ItemCategory EGGS = get("minecraft:eggs"); public static final ItemCategory EMERALD_ORES = get("minecraft:emerald_ores"); public static final ItemCategory ENCHANTABLE_ARMOR = get("minecraft:enchantable/armor"); @@ -121,6 +124,7 @@ public final class ItemCategories { public static final ItemCategory GOAT_FOOD = get("minecraft:goat_food"); public static final ItemCategory GOLD_ORES = get("minecraft:gold_ores"); public static final ItemCategory GOLD_TOOL_MATERIALS = get("minecraft:gold_tool_materials"); + public static final ItemCategory GRASS_BLOCKS = get("minecraft:grass_blocks"); public static final ItemCategory HANGING_SIGNS = get("minecraft:hanging_signs"); public static final ItemCategory HEAD_ARMOR = get("minecraft:head_armor"); public static final ItemCategory HOES = get("minecraft:hoes"); @@ -141,9 +145,14 @@ public final class ItemCategories { public static final ItemCategory LLAMA_TEMPT_ITEMS = get("minecraft:llama_tempt_items"); public static final ItemCategory LOGS = get("minecraft:logs"); public static final ItemCategory LOGS_THAT_BURN = get("minecraft:logs_that_burn"); + public static final ItemCategory LOOM_DYES = get("minecraft:loom_dyes"); + public static final ItemCategory LOOM_PATTERNS = get("minecraft:loom_patterns"); public static final ItemCategory MANGROVE_LOGS = get("minecraft:mangrove_logs"); public static final ItemCategory MAP_INVISIBILITY_EQUIPMENT = get("minecraft:map_invisibility_equipment"); public static final ItemCategory MEAT = get("minecraft:meat"); + public static final ItemCategory METAL_NUGGETS = get("minecraft:metal_nuggets"); + public static final ItemCategory MOSS_BLOCKS = get("minecraft:moss_blocks"); + public static final ItemCategory MUD = get("minecraft:mud"); @Deprecated public static final ItemCategory MUSIC_DISCS = get("minecraft:music_discs"); public static final ItemCategory NAUTILUS_BUCKET_FOOD = get("minecraft:nautilus_bucket_food"); public static final ItemCategory NAUTILUS_FOOD = get("minecraft:nautilus_food"); @@ -218,6 +227,8 @@ public final class ItemCategories { public static final ItemCategory WALLS = get("minecraft:walls"); public static final ItemCategory WARPED_STEMS = get("minecraft:warped_stems"); public static final ItemCategory WART_BLOCKS = get("minecraft:wart_blocks"); + public static final ItemCategory WITHER_SKELETON_DISLIKED_WEAPONS = get("minecraft:wither_skeleton_disliked_weapons"); + public static final ItemCategory WOLF_COLLAR_DYES = get("minecraft:wolf_collar_dyes"); public static final ItemCategory WOLF_FOOD = get("minecraft:wolf_food"); public static final ItemCategory WOODEN_BUTTONS = get("minecraft:wooden_buttons"); public static final ItemCategory WOODEN_DOORS = get("minecraft:wooden_doors"); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/item/ItemCategory.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/item/ItemCategory.java index 28811bc0f3..d9a5058846 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/item/ItemCategory.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/item/ItemCategory.java @@ -34,7 +34,7 @@ */ public class ItemCategory extends Category implements Keyed { - public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("item tag"); + public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("item tag", "item_tag", "minecraft"); public ItemCategory(final String id) { super(id); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/item/ItemType.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/item/ItemType.java index e7a72b3c91..69d10ec492 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/item/ItemType.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/item/ItemType.java @@ -39,7 +39,7 @@ public class ItemType implements RegistryItem, Keyed { //FAWE end - public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("item type", true); + public static final NamespacedRegistry REGISTRY = new NamespacedRegistry<>("item type", "item_type", "minecraft", true); private final String id; @SuppressWarnings({"deprecation", "this-escape"}) diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/item/ItemTypes.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/item/ItemTypes.java index d512afb52c..a981390142 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/item/ItemTypes.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/item/ItemTypes.java @@ -1156,6 +1156,8 @@ public final class ItemTypes { @Nullable public static final ItemType GOLDEN_CHESTPLATE = init(); @Nullable + public static final ItemType GOLDEN_DANDELION = get("minecraft:golden_dandelion"); + @Nullable public static final ItemType GOLDEN_HELMET = init(); @Nullable public static final ItemType GOLDEN_HOE = init(); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/world/weather/WeatherType.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/weather/WeatherType.java index 4a7b3cd0c9..05f3ff0846 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/world/weather/WeatherType.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/world/weather/WeatherType.java @@ -23,7 +23,7 @@ import com.sk89q.worldedit.registry.Registry; public record WeatherType(String id) implements Keyed { - public static final Registry REGISTRY = new Registry<>("weather type"); + public static final Registry REGISTRY = new Registry<>("weather type", "weather_type"); /** * Gets the name of this weather, or the ID if the name cannot be found. diff --git a/worldedit-core/src/main/resources/lang/strings.json b/worldedit-core/src/main/resources/lang/strings.json index a4843811ed..0eccfff93b 100644 --- a/worldedit-core/src/main/resources/lang/strings.json +++ b/worldedit-core/src/main/resources/lang/strings.json @@ -68,8 +68,12 @@ "fawe.worldedit.schematic.schematic.move.failed": "{0} no moved: {1}", "fawe.worldedit.schematic.schematic.loaded": "{0} loaded. Paste it with //paste", "fawe.worldedit.schematic.schematic.saved": "{0} saved.", + "fawe.worldedit.schematic.schematic.overwritten": "{0} saved (overwriting previous file).", "fawe.worldedit.schematic.schematic.none": "No files found.", "fawe.worldedit.schematic.schematic.load-failure": "File could not be read or it does not exist: {0}. If you are specifying a format, you may not be specifying the correct one. Sponge schematic v2 and v3 both use the .schem file extension. To allow FAWE to select the format, do not specify one. If you are using a litematica schematic, it is not supported!", + "fawe.worldedit.schematic.schematic.size": "{0} size: {1}kb", + "fawe.worldedit.schematic.schematic.disk.space": "You have {0}kb left for schematics.", + "fawe.worldedit.schematic.schematic.slots.free": "You have {0} schematic file slots left.", "fawe.worldedit.clipboard.clipboard.uri.not.found": "You do not have {0} loaded", "fawe.worldedit.clipboard.clipboard.cleared": "Clipboard cleared", "fawe.worldedit.clipboard.clipboard.invalid.format": "Unknown clipboard format: {0}", @@ -125,6 +129,8 @@ "fawe.error.input-parser-exception": "Invalid empty string instead of boolean.", "fawe.error.invalid-boolean": "Invalid boolean {0}", "fawe.error.schematic.not.found": "Schematic {0} not found.", + "fawe.error.schematic.over.count.limit": "You have {0}/{1} saved schematics. Delete some to save this one!", + "fawe.error.schematic.over.disk.limit": "You're about to be at {0}kb of schematics ({1} available). Delete some first to save this one!", "fawe.error.parse.invalid-dangling-character": "Invalid dangling character {0}.", "fawe.error.parse.unknown-mask": "Unknown mask: {0}, See: {1}", "fawe.error.parse.unknown-pattern": "Unknown pattern: {0}, See: {1}", @@ -553,6 +559,8 @@ "worldedit.pastebin.uploading": "(Please wait... sending output to paste service...)", "worldedit.session.cant-find-session": "Unable to find session for {0}", "worldedit.platform.no-file-dialog": "File dialogs are not supported in your environment.", + "worldedit.registry.error.invalid-key": "Invalid registry key: {0}", + "worldedit.registry.searching": "(Please wait... searching registry...)", "worldedit.tool.max-block-changes": "Max blocks change limit reached.", "worldedit.tool.no-block": "No block in sight!", "worldedit.tool.repl.equip": "Block replacer tool bound to {0}.",