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/build-logic/src/main/kotlin/buildlogic.adapter.gradle.kts b/build-logic/src/main/kotlin/buildlogic.adapter.gradle.kts index 4fe0aa6b45..abf009fd03 100644 --- a/build-logic/src/main/kotlin/buildlogic.adapter.gradle.kts +++ b/build-logic/src/main/kotlin/buildlogic.adapter.gradle.kts @@ -18,23 +18,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,6 +52,11 @@ dependencies { } } +java { + // Required when we de-sync release option and declared Java versions. + disableAutoTargetJvm() +} + tasks.named("assemble") { dependsOn("reobfJar") } diff --git a/build.gradle.kts b/build.gradle.kts index 37ea2f31e1..83ac9fdc61 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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") tasks { supportedVersions.forEach { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cf502f455b..4ddb8f25cc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -55,7 +55,7 @@ pluginyml = "0.6.0" mod-publish-plugin = "1.1.0" grgit = "5.3.3" shadow = "9.4.1" -paperweight = "2.0.0-beta.19" +paperweight = "2.0.0-beta.21" codecov = "0.2.0" 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_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_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 eb3b0b6d20..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 @@ -231,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); 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..f60f9398b9 --- /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.1.build.20-alpha") + 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..03ab881743 --- /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,908 @@ +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.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.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.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(); + } + @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 = TaskManager.taskManager().sync(() -> { + 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 = TaskManager.taskManager().sync(() -> { + 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 = TaskManager.taskManager().sync(() -> { + 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..7f3c308154 --- /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,143 @@ +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..f4d94c5b7a --- /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,297 @@ +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.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(MinecraftServer.currentTick); + } + + 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 = MinecraftServer.currentTick; + 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 + ) { + + } + +} 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..c52649ff60 --- /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,1069 @@ +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.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.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) { + BlockEntity blockEntity = getChunk().getBlockEntity(new BlockPos((x & 15) + ( + chunkX << 4), y, (z & 15) + ( + chunkZ << 4))); + if (blockEntity == null) { + return null; + } + return NMS_TO_TILE.apply(blockEntity); + + } + + @Override + public Map tiles() { + Map nmsTiles = getChunk().getBlockEntities(); + if (nmsTiles.isEmpty()) { + return Collections.emptyMap(); + } + return AdaptedMap.immutable(nmsTiles, 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 beacons = 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 (PaperLib.isPaper() && tile instanceof BeaconBlockEntity) { + if (beacons == null) { + beacons = new ArrayList<>(); + } + beacons.add(tile); + PaperweightPlatformAdapter.removeBeacon(tile, nmsChunk); + continue; + } + nmsChunk.removeBlockEntity(tile.getBlockPos()); + if (createCopy) { + copy.storeTile(tile); + } + } + } + } + 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 (beacons != null && !beacons.isEmpty()) { + final List finalBeacons = beacons; + + syncTasks.add(() -> { + for (BlockEntity beacon : finalBeacons) { + BeaconBlockEntity.playSound(beacon.getLevel(), beacon.getBlockPos(), SoundEvents.BEACON_DEACTIVATE); + new BeaconDeactivatedEvent(CraftBlock.at(beacon.getLevel(), beacon.getBlockPos())).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 (!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); + + 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..7f72745eff --- /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); + 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..9a565f632e --- /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,632 @@ +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.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.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) { + // 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; + } + 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..a048cc7a92 --- /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,267 @@ +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.google.common.collect.ImmutableList; +import com.mojang.serialization.Lifecycle; +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 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.Level; +import net.minecraft.world.level.LevelSettings; +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.WorldOptions; +import net.minecraft.world.level.storage.LevelData; +import net.minecraft.world.level.storage.LevelStorageSource; +import net.minecraft.world.level.storage.PrimaryLevelData; +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.CraftWorld; +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.function.BooleanSupplier; +import java.util.function.Supplier; + +import static net.minecraft.core.registries.Registries.BIOME; + +public class PaperweightRegen extends Regenerator { + + 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 { + //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("faweregentempworld", levelStemResourceKey); + LevelData originalWorldData = originalServerWorld.serverLevelData; + + MinecraftServer server = originalServerWorld.getCraftServer().getServer(); + WorldOptions originalOpts = originalWorldData.worldGenOptions(); + WorldOptions newOpts = options.getSeed().isPresent() + ? originalOpts.withSeed(OptionalLong.of(seed)) + : originalOpts; + LevelSettings newWorldSettings = new LevelSettings( + "faweregentempworld", + originalWorldData.settings.gameType(), + originalWorldData.settings.hardcore(), + originalWorldData.settings.difficulty(), + originalWorldData.settings.allowCommands(), + originalWorldData.settings.gameRules(), + originalWorldData.settings.getDataConfiguration() + ); + + PrimaryLevelData.SpecialWorldProperty specialWorldProperty = + originalWorldData.isFlatWorld() + ? PrimaryLevelData.SpecialWorldProperty.FLAT + : originalWorldData.isDebugWorld() + ? PrimaryLevelData.SpecialWorldProperty.DEBUG + : PrimaryLevelData.SpecialWorldProperty.NONE; + PrimaryLevelData newWorldData = new PrimaryLevelData(newWorldSettings, newOpts, specialWorldProperty, Lifecycle.stable()); + + BiomeProvider biomeProvider = getBiomeProvider(); + + + //init world + freshWorld = Fawe.instance().getQueueHandler().sync((Supplier) () -> new ServerLevel( + server, + server.executor, + session, + newWorldData, + originalServerWorld.dimension(), + new LevelStem( + originalServerWorld.dimensionTypeRegistration(), + originalServerWorld.getChunkSource().getGenerator() + ), + originalServerWorld.isDebug(), + seed, + ImmutableList.of(), + false, + originalServerWorld.getRandomSequences(), + environment, + generator, + biomeProvider + ) { + + 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(); + newWorldData.checkName(originalServerWorld.serverLevelData.getLevelName()); //rename to original world name + if (paperConfigField != null) { + paperConfigField.set(freshWorld, originalServerWorld.paperConfig()); + } + return true; + } + + @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 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("faweregentempworld"); + } 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..9a9667a6a8 --- /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,1254 @@ +/* + * 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.ImmutableList; +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.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.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 com.sk89q.worldedit.world.registry.BlockMaterial; +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.StringRepresentable; +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.Locale; +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 { + 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-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/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/BlockTypes.java b/worldedit-core/src/main/java/com/sk89q/worldedit/world/block/BlockTypes.java index 9d59912235..1348c4b6c5 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 = get("minecraft:golden_dandelion"); + @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 = get("minecraft:potted_golden_dandelion"); + @Nullable public static final BlockType POTTED_JUNGLE_SAPLING = init(); @Nullable public static final BlockType POTTED_LILY_OF_THE_VALLEY = init(); 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/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();