diff --git a/build.gradle b/build.gradle
index 80c9e31f2..dddd5470e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -30,7 +30,7 @@ println """
1) Read COMPILING.md if you haven't yet
2) Try running 'build' in a separate Gradle run
3) Use gradlew and not gradle
- 4) If you still need help, ask on IRC! irc.esper.net #sk89q
+ 4) If you still need help, ask on Discord! https://discord.gg/enginehub
Output files will be in [subproject]/build/libs
*******************************************
@@ -85,7 +85,7 @@ subprojects {
}
}
-configure(['core', 'bukkit', 'forge', 'sponge'].collect { project(":worldedit-$it") }) {
+configure(['core', 'bukkit', 'forge', 'sponge', 'fabric'].collect { project(":worldedit-$it") }) {
apply plugin: 'java'
apply plugin: 'maven'
apply plugin: 'checkstyle'
@@ -118,7 +118,7 @@ configure(['core', 'bukkit', 'forge', 'sponge'].collect { project(":worldedit-$i
archives javadocJar
}
- if (!(name.equals('worldedit-forge') || name.equals('worldedit-sponge'))) {
+ if (name == "worldedit-core" || name == "worldedit-bukkit") {
task sourcesJar(type: Jar, dependsOn: classes) {
classifier = 'sources'
from sourceSets.main.allSource
@@ -144,7 +144,7 @@ configure(['core', 'bukkit', 'forge', 'sponge'].collect { project(":worldedit-$i
}
}
-configure(['bukkit', 'forge', 'sponge'].collect { project(":worldedit-$it") }) {
+configure(['bukkit', 'forge', 'sponge', 'fabric'].collect { project(":worldedit-$it") }) {
shadowJar {
classifier 'dist'
dependencies {
diff --git a/config/checkstyle/import-control.xml b/config/checkstyle/import-control.xml
index a291b5ad5..27b054a03 100644
--- a/config/checkstyle/import-control.xml
+++ b/config/checkstyle/import-control.xml
@@ -60,6 +60,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/settings.gradle b/settings.gradle
index dec0467a5..a7c14e8ee 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -2,7 +2,7 @@ rootProject.name = 'worldedit'
include 'worldedit-libs'
-['bukkit', 'core', 'forge', 'sponge'].forEach {
+['bukkit', 'core', 'forge', 'sponge', 'fabric'].forEach {
include "worldedit-libs:$it"
include "worldedit-$it"
}
diff --git a/worldedit-fabric/build.gradle b/worldedit-fabric/build.gradle
new file mode 100644
index 000000000..6045e3679
--- /dev/null
+++ b/worldedit-fabric/build.gradle
@@ -0,0 +1,105 @@
+import net.fabricmc.loom.task.RemapJarTask
+
+buildscript {
+ repositories {
+ jcenter()
+ maven {
+ name = 'Fabric'
+ url = 'https://maven.fabricmc.net/'
+ }
+ maven {
+ name = 'sponge'
+ url = 'https://repo.spongepowered.org/maven'
+ }
+ }
+
+ dependencies {
+ classpath 'net.fabricmc:fabric-loom:0.2.4-SNAPSHOT'
+ classpath 'org.spongepowered:mixin:0.7.11-SNAPSHOT'
+ }
+}
+
+apply plugin: 'eclipse'
+apply plugin: 'fabric-loom'
+
+def minecraftVersion = "1.14.2"
+def fabricVersion = "0.3.0+build.185"
+def yarnMappings = "1.14.2+build.7"
+def loaderVersion = "0.4.8+build.155"
+
+configurations.all { Configuration it ->
+ it.resolutionStrategy { ResolutionStrategy rs ->
+ rs.force("com.google.guava:guava:21.0")
+ }
+}
+
+dependencies {
+ compile project(':worldedit-core')
+ compile 'org.apache.logging.log4j:log4j-slf4j-impl:2.8.1'
+
+ minecraft "com.mojang:minecraft:${minecraftVersion}"
+ mappings "net.fabricmc:yarn:${yarnMappings}"
+ modCompile "net.fabricmc:fabric-loader:${loaderVersion}"
+
+ modCompile "net.fabricmc.fabric-api:fabric-api:${fabricVersion}"
+
+ testCompile group: 'org.mockito', name: 'mockito-core', version: '1.9.0-rc1'
+}
+
+sourceCompatibility = 1.8
+targetCompatibility = 1.8
+
+minecraft {
+}
+
+project.archivesBaseName = "${project.archivesBaseName}-mc${minecraftVersion}"
+
+processResources {
+ // this will ensure that this task is redone when the versions change.
+ inputs.property 'version', project.internalVersion
+
+ from(sourceSets.main.resources.srcDirs) {
+ include "fabric.mod.json"
+ expand "version": project.internalVersion
+ }
+
+ // copy everything else except the mod json
+ from(sourceSets.main.resources.srcDirs) {
+ exclude "fabric.mod.json"
+ }
+}
+
+jar {
+ manifest {
+ attributes("Class-Path": "truezip.jar WorldEdit/truezip.jar js.jar WorldEdit/js.jar",
+ "WorldEdit-Version": version)
+ }
+}
+
+shadowJar {
+ classifier = 'dist-dev'
+ dependencies {
+ relocate "org.slf4j", "com.sk89q.worldedit.slf4j"
+ relocate "org.apache.logging.slf4j", "com.sk89q.worldedit.log4jbridge"
+
+ include(dependency('org.slf4j:slf4j-api'))
+ include(dependency("org.apache.logging.log4j:log4j-slf4j-impl"))
+ }
+}
+
+task deobfJar(type: Jar) {
+ from sourceSets.main.output
+ classifier = 'dev'
+}
+
+artifacts {
+ archives deobfJar
+}
+
+task shadowJarRemap(type: RemapJarTask) {
+ input shadowJar.archivePath
+ output new File(shadowJar.archivePath.getAbsolutePath().replaceFirst('-dev\\.jar$', ".jar"))
+}
+
+shadowJarRemap.dependsOn(shadowJar)
+build.dependsOn(shadowJarRemap)
\ No newline at end of file
diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/CommandWrapper.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/CommandWrapper.java
new file mode 100644
index 000000000..8efa5ce97
--- /dev/null
+++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/CommandWrapper.java
@@ -0,0 +1,128 @@
+/*
+ * 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 Lesser 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.fabric;
+
+import static com.sk89q.worldedit.fabric.FabricAdapter.adaptPlayer;
+import static net.minecraft.server.command.CommandManager.argument;
+import static net.minecraft.server.command.CommandManager.literal;
+
+import com.google.common.collect.ImmutableList;
+import com.mojang.brigadier.Command;
+import com.mojang.brigadier.CommandDispatcher;
+import com.mojang.brigadier.arguments.StringArgumentType;
+import com.mojang.brigadier.builder.LiteralArgumentBuilder;
+import com.mojang.brigadier.context.CommandContext;
+import com.mojang.brigadier.context.StringRange;
+import com.mojang.brigadier.exceptions.CommandSyntaxException;
+import com.mojang.brigadier.suggestion.Suggestion;
+import com.mojang.brigadier.suggestion.Suggestions;
+import com.mojang.brigadier.suggestion.SuggestionsBuilder;
+import com.sk89q.worldedit.WorldEdit;
+import com.sk89q.worldedit.event.platform.CommandSuggestionEvent;
+import com.sk89q.worldedit.extension.platform.Actor;
+import com.sk89q.worldedit.internal.util.Substring;
+import net.minecraft.entity.Entity;
+import net.minecraft.server.command.ServerCommandSource;
+import net.minecraft.server.network.ServerPlayerEntity;
+import org.enginehub.piston.inject.InjectedValueStore;
+import org.enginehub.piston.inject.Key;
+import org.enginehub.piston.inject.MapBackedValueStore;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Predicate;
+
+
+public final class CommandWrapper {
+
+ private CommandWrapper() {
+ }
+
+ public static void register(CommandDispatcher dispatcher, org.enginehub.piston.Command command) {
+ ImmutableList.Builder aliases = ImmutableList.builder();
+ aliases.add(command.getName()).addAll(command.getAliases());
+
+ Command commandRunner =
+ ctx -> {
+ WorldEdit.getInstance().getEventBus().post(new com.sk89q.worldedit.event.platform.CommandEvent(
+ adaptPlayer(ctx.getSource().getPlayer()),
+ ctx.getInput()
+ ));
+ return 0;
+ };
+
+ for (String alias : aliases.build()) {
+ LiteralArgumentBuilder base = literal(alias).executes(commandRunner)
+ .then(argument("args", StringArgumentType.greedyString())
+ .suggests(CommandWrapper::suggest)
+ .executes(commandRunner));
+ if (command.getCondition() != org.enginehub.piston.Command.Condition.TRUE) {
+ base.requires(requirementsFor(command));
+ }
+ dispatcher.register(base);
+ }
+ }
+
+ private static Predicate requirementsFor(org.enginehub.piston.Command mapping) {
+ return ctx -> {
+ final Entity entity = ctx.getEntity();
+ if (!(entity instanceof ServerPlayerEntity)) {
+ return true;
+ }
+ final Actor actor = FabricAdapter.adaptPlayer(((ServerPlayerEntity) entity));
+ InjectedValueStore store = MapBackedValueStore.create();
+ store.injectValue(Key.of(Actor.class), context -> Optional.of(actor));
+ return mapping.getCondition().satisfied(store);
+ };
+ }
+
+ private static CompletableFuture suggest(CommandContext context,
+ SuggestionsBuilder builder) throws CommandSyntaxException {
+ CommandSuggestionEvent event = new CommandSuggestionEvent(
+ FabricAdapter.adaptPlayer(context.getSource().getPlayer()),
+ builder.getInput()
+ );
+ WorldEdit.getInstance().getEventBus().post(event);
+ List suggestions = event.getSuggestions();
+
+ ImmutableList.Builder result = ImmutableList.builder();
+
+ for (Substring suggestion : suggestions) {
+ String suggestionText = suggestion.getSubstring();
+ // If at end, we are actually suggesting the next argument
+ // Ensure there is a space!
+ if (suggestion.getStart() == suggestion.getEnd()
+ && suggestion.getEnd() == builder.getInput().length()
+ && !builder.getInput().endsWith(" ")) {
+ suggestionText = " " + suggestionText;
+ }
+ result.add(new Suggestion(
+ StringRange.between(suggestion.getStart(), suggestion.getEnd()),
+ suggestionText
+ ));
+ }
+
+ return CompletableFuture.completedFuture(
+ Suggestions.create(builder.getInput(), result.build())
+ );
+ }
+
+}
diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricAdapter.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricAdapter.java
new file mode 100644
index 000000000..317274331
--- /dev/null
+++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricAdapter.java
@@ -0,0 +1,241 @@
+/*
+ * 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 Lesser 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.fabric;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.collect.ImmutableList;
+import com.sk89q.jnbt.CompoundTag;
+import com.sk89q.jnbt.Tag;
+import com.sk89q.worldedit.blocks.BaseItemStack;
+import com.sk89q.worldedit.math.BlockVector3;
+import com.sk89q.worldedit.math.Vector3;
+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.world.World;
+import com.sk89q.worldedit.world.biome.BiomeType;
+import com.sk89q.worldedit.world.biome.BiomeTypes;
+import com.sk89q.worldedit.world.block.BlockState;
+import com.sk89q.worldedit.world.block.BlockType;
+import com.sk89q.worldedit.world.block.BlockTypes;
+import com.sk89q.worldedit.world.item.ItemType;
+import com.sk89q.worldedit.world.item.ItemTypes;
+import net.minecraft.block.Block;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.server.network.ServerPlayerEntity;
+import net.minecraft.state.StateFactory;
+import net.minecraft.state.property.DirectionProperty;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.StringIdentifiable;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.Vec3d;
+import net.minecraft.util.registry.Registry;
+import net.minecraft.world.biome.Biome;
+
+import java.util.Comparator;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.stream.Collectors;
+
+public final class FabricAdapter {
+
+ private FabricAdapter() {
+ }
+
+ public static World adapt(net.minecraft.world.World world) {
+ return new FabricWorld(world);
+ }
+
+ public static Biome adapt(BiomeType biomeType) {
+ return Registry.BIOME.get(new Identifier(biomeType.getId()));
+ }
+
+ public static BiomeType adapt(Biome biome) {
+ return BiomeTypes.get(Registry.BIOME.getId(biome).toString());
+ }
+
+ public static Vector3 adapt(Vec3d vector) {
+ return Vector3.at(vector.x, vector.y, vector.z);
+ }
+
+ public static BlockVector3 adapt(BlockPos pos) {
+ return BlockVector3.at(pos.getX(), pos.getY(), pos.getZ());
+ }
+
+ public static Vec3d toVec3(BlockVector3 vector) {
+ return new Vec3d(vector.getBlockX(), vector.getBlockY(), vector.getBlockZ());
+ }
+
+ public static net.minecraft.util.math.Direction adapt(Direction face) {
+ switch (face) {
+ case NORTH: return net.minecraft.util.math.Direction.NORTH;
+ case SOUTH: return net.minecraft.util.math.Direction.SOUTH;
+ case WEST: return net.minecraft.util.math.Direction.WEST;
+ case EAST: return net.minecraft.util.math.Direction.EAST;
+ case DOWN: return net.minecraft.util.math.Direction.DOWN;
+ case UP:
+ default:
+ return net.minecraft.util.math.Direction.UP;
+ }
+ }
+
+ public static Direction adaptEnumFacing(net.minecraft.util.math.Direction face) {
+ switch (face) {
+ case NORTH: return Direction.NORTH;
+ case SOUTH: return Direction.SOUTH;
+ case WEST: return Direction.WEST;
+ case EAST: return Direction.EAST;
+ case DOWN: return Direction.DOWN;
+ case UP:
+ default:
+ return Direction.UP;
+ }
+ }
+
+ public static BlockPos toBlockPos(BlockVector3 vector) {
+ return new BlockPos(vector.getBlockX(), vector.getBlockY(), vector.getBlockZ());
+ }
+
+ public static Property> adaptProperty(net.minecraft.state.property.Property> property) {
+ if (property instanceof net.minecraft.state.property.BooleanProperty) {
+ return new BooleanProperty(property.getName(), ImmutableList.copyOf(((net.minecraft.state.property.BooleanProperty) property).getValues()));
+ }
+ if (property instanceof net.minecraft.state.property.IntProperty) {
+ return new IntegerProperty(property.getName(), ImmutableList.copyOf(((net.minecraft.state.property.IntProperty) property).getValues()));
+ }
+ if (property instanceof DirectionProperty) {
+ return new DirectionalProperty(property.getName(), ((DirectionProperty) property).getValues().stream()
+ .map(FabricAdapter::adaptEnumFacing)
+ .collect(Collectors.toList()));
+ }
+ if (property instanceof net.minecraft.state.property.EnumProperty) {
+ // Note: do not make x.asString a method reference.
+ // It will cause runtime bootstrap exceptions.
+ return new EnumProperty(property.getName(), ((net.minecraft.state.property.EnumProperty>) property).getValues().stream()
+ .map(x -> x.asString())
+ .collect(Collectors.toList()));
+ }
+ return new PropertyAdapter<>(property);
+ }
+
+ public static Map, Object> adaptProperties(BlockType block, Map, Comparable>> mcProps) {
+ Map, Object> props = new TreeMap<>(Comparator.comparing(Property::getName));
+ for (Map.Entry, Comparable>> prop : mcProps.entrySet()) {
+ Object value = prop.getValue();
+ if (prop.getKey() instanceof DirectionProperty) {
+ value = adaptEnumFacing((net.minecraft.util.math.Direction) value);
+ } else if (prop.getKey() instanceof net.minecraft.state.property.EnumProperty) {
+ value = ((StringIdentifiable) value).asString();
+ }
+ props.put(block.getProperty(prop.getKey().getName()), value);
+ }
+ return props;
+ }
+
+ private static net.minecraft.block.BlockState applyProperties(StateFactory stateContainer,
+ net.minecraft.block.BlockState newState, Map, Object> states) {
+ for (Map.Entry, Object> state : states.entrySet()) {
+ net.minecraft.state.property.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 DirectionProperty) {
+ Direction dir = (Direction) value;
+ value = adapt(dir);
+ } else if (property instanceof net.minecraft.state.property.EnumProperty) {
+ String enumName = (String) value;
+ value = ((net.minecraft.state.property.EnumProperty>) property).getValue((String) value).orElseGet(() -> {
+ throw new IllegalStateException("Enum property " + property.getName() + " does not contain " + enumName);
+ });
+ }
+
+ newState = newState.with(property, value);
+ }
+ return newState;
+ }
+
+ public static net.minecraft.block.BlockState adapt(BlockState blockState) {
+ Block mcBlock = adapt(blockState.getBlockType());
+ net.minecraft.block.BlockState newState = mcBlock.getDefaultState();
+ Map, Object> states = blockState.getStates();
+ return applyProperties(mcBlock.getStateFactory(), newState, states);
+ }
+
+ public static BlockState adapt(net.minecraft.block.BlockState blockState) {
+ BlockType blockType = adapt(blockState.getBlock());
+ return blockType.getState(adaptProperties(blockType, blockState.getEntries()));
+ }
+
+ public static Block adapt(BlockType blockType) {
+ return Registry.BLOCK.get(new Identifier(blockType.getId()));
+ }
+
+ public static BlockType adapt(Block block) {
+ return BlockTypes.get(Registry.BLOCK.getId(block).toString());
+ }
+
+ public static Item adapt(ItemType itemType) {
+ return Registry.ITEM.get(new Identifier(itemType.getId()));
+ }
+
+ public static ItemType adapt(Item item) {
+ return ItemTypes.get(Registry.ITEM.getId(item).toString());
+ }
+
+ public static ItemStack adapt(BaseItemStack baseItemStack) {
+ net.minecraft.nbt.CompoundTag fabricCompound = null;
+ if (baseItemStack.getNbtData() != null) {
+ fabricCompound = NBTConverter.toNative(baseItemStack.getNbtData());
+ }
+ final ItemStack itemStack = new ItemStack(adapt(baseItemStack.getType()), baseItemStack.getAmount());
+ itemStack.setTag(fabricCompound);
+ return itemStack;
+ }
+
+ public static BaseItemStack adapt(ItemStack itemStack) {
+ CompoundTag tag = NBTConverter.fromNative(itemStack.toTag(new net.minecraft.nbt.CompoundTag()));
+ if (tag.getValue().isEmpty()) {
+ tag = null;
+ } else {
+ final Tag tagTag = tag.getValue().get("tag");
+ if (tagTag instanceof CompoundTag) {
+ tag = ((CompoundTag) tagTag);
+ } else {
+ tag = null;
+ }
+ }
+ return new BaseItemStack(adapt(itemStack.getItem()), tag, itemStack.getCount());
+ }
+
+ /**
+ * Get the WorldEdit proxy for the given player.
+ *
+ * @param player the player
+ * @return the WorldEdit player
+ */
+ public static FabricPlayer adaptPlayer(ServerPlayerEntity player) {
+ checkNotNull(player);
+ return new FabricPlayer(player);
+ }
+}
diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricBiomeRegistry.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricBiomeRegistry.java
new file mode 100644
index 000000000..fbf883101
--- /dev/null
+++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricBiomeRegistry.java
@@ -0,0 +1,58 @@
+/*
+ * 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 Lesser 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.fabric;
+
+import com.sk89q.worldedit.world.biome.BiomeData;
+import com.sk89q.worldedit.world.biome.BiomeType;
+import com.sk89q.worldedit.world.registry.BiomeRegistry;
+import net.minecraft.world.biome.Biome;
+
+/**
+ * Provides access to biome data in Fabric.
+ */
+class FabricBiomeRegistry implements BiomeRegistry {
+
+ @Override
+ public BiomeData getData(BiomeType biome) {
+ return new FabricBiomeData(FabricAdapter.adapt(biome));
+ }
+
+ /**
+ * Cached biome data information.
+ */
+ private static class FabricBiomeData implements BiomeData {
+ private final Biome biome;
+
+ /**
+ * Create a new instance.
+ *
+ * @param biome the base biome
+ */
+ private FabricBiomeData(Biome biome) {
+ this.biome = biome;
+ }
+
+ @Override
+ public String getName() {
+ return biome.getTextComponent().getString();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricBlockCategoryRegistry.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricBlockCategoryRegistry.java
new file mode 100644
index 000000000..bbf6ffbd3
--- /dev/null
+++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricBlockCategoryRegistry.java
@@ -0,0 +1,46 @@
+/*
+ * 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 Lesser 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.fabric;
+
+import com.sk89q.worldedit.registry.Category;
+import com.sk89q.worldedit.world.block.BlockType;
+import com.sk89q.worldedit.world.registry.BlockCategoryRegistry;
+import net.minecraft.tag.BlockTags;
+import net.minecraft.tag.Tag;
+import net.minecraft.util.Identifier;
+
+import java.util.Collections;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class FabricBlockCategoryRegistry implements BlockCategoryRegistry {
+ @Override
+ public Set getCategorisedByName(String category) {
+ return Optional.ofNullable(BlockTags.getContainer().get(new Identifier(category)))
+ .map(Tag::values).orElse(Collections.emptySet())
+ .stream().map(FabricAdapter::adapt).collect(Collectors.toSet());
+ }
+
+ @Override
+ public Set getAll(Category category) {
+ return getCategorisedByName(category.getId());
+ }
+}
diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricBlockMaterial.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricBlockMaterial.java
new file mode 100644
index 000000000..c9fc86e02
--- /dev/null
+++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricBlockMaterial.java
@@ -0,0 +1,93 @@
+/*
+ * 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 Lesser 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.fabric;
+
+import com.sk89q.worldedit.world.registry.BlockMaterial;
+import com.sk89q.worldedit.world.registry.PassthroughBlockMaterial;
+import net.minecraft.block.Material;
+import net.minecraft.block.piston.PistonBehavior;
+
+import javax.annotation.Nullable;
+
+/**
+ * Fabric block material that pulls as much info as possible from the Minecraft
+ * Material, and passes the rest to another implementation, typically the
+ * bundled block info.
+ */
+public class FabricBlockMaterial extends PassthroughBlockMaterial {
+
+ private final Material delegate;
+
+ public FabricBlockMaterial(Material delegate, @Nullable BlockMaterial secondary) {
+ super(secondary);
+ this.delegate = delegate;
+ }
+
+ @Override
+ public boolean isAir() {
+ return delegate == Material.AIR || super.isAir();
+ }
+
+ @Override
+ public boolean isOpaque() {
+ return delegate.blocksLight();
+ }
+
+ @Override
+ public boolean isLiquid() {
+ return delegate.isLiquid();
+ }
+
+ @Override
+ public boolean isSolid() {
+ return delegate.isSolid();
+ }
+
+ @Override
+ public boolean isFragileWhenPushed() {
+ return delegate.getPistonBehavior() == PistonBehavior.DESTROY;
+ }
+
+ @Override
+ public boolean isUnpushable() {
+ return delegate.getPistonBehavior() == PistonBehavior.BLOCK;
+ }
+
+ @Override
+ public boolean isMovementBlocker() {
+ return delegate.blocksMovement();
+ }
+
+ @Override
+ public boolean isBurnable() {
+ return delegate.isBurnable();
+ }
+
+ @Override
+ public boolean isToolRequired() {
+ return !delegate.canBreakByHand();
+ }
+
+ @Override
+ public boolean isReplacedDuringPlacement() {
+ return delegate.isReplaceable();
+ }
+
+}
diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricBlockRegistry.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricBlockRegistry.java
new file mode 100644
index 000000000..1d219c90c
--- /dev/null
+++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricBlockRegistry.java
@@ -0,0 +1,80 @@
+/*
+ * 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 Lesser 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.fabric;
+
+import com.sk89q.worldedit.registry.state.Property;
+import com.sk89q.worldedit.world.block.BlockState;
+import com.sk89q.worldedit.world.block.BlockType;
+import com.sk89q.worldedit.world.registry.BlockMaterial;
+import com.sk89q.worldedit.world.registry.BundledBlockRegistry;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.loader.api.FabricLoader;
+import net.minecraft.block.Block;
+import net.minecraft.block.Material;
+
+import javax.annotation.Nullable;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.OptionalInt;
+import java.util.TreeMap;
+
+public class FabricBlockRegistry extends BundledBlockRegistry {
+
+ private Map materialMap = new HashMap<>();
+
+ @Nullable
+ @Override
+ public String getName(BlockType blockType) {
+ Block block = FabricAdapter.adapt(blockType);
+ if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) {
+ return block.getTextComponent().getFormattedText();
+ } else {
+ return super.getName(blockType);
+ }
+ }
+
+ @Override
+ public BlockMaterial getMaterial(BlockType blockType) {
+ Block block = FabricAdapter.adapt(blockType);
+ return materialMap.computeIfAbsent(block.getDefaultState().getMaterial(),
+ m -> new FabricBlockMaterial(m, super.getMaterial(blockType)));
+ }
+
+ @Override
+ public Map> getProperties(BlockType blockType) {
+ Block block = FabricAdapter.adapt(blockType);
+ Map> map = new TreeMap<>();
+ Collection> propertyKeys = block
+ .getDefaultState()
+ .getProperties();
+ for (net.minecraft.state.property.Property> key : propertyKeys) {
+ map.put(key.getName(), FabricAdapter.adaptProperty(key));
+ }
+ return map;
+ }
+
+ @Override
+ public OptionalInt getInternalBlockStateId(BlockState state) {
+ net.minecraft.block.BlockState equivalent = FabricAdapter.adapt(state);
+ return OptionalInt.of(Block.getRawIdFromState(equivalent));
+ }
+}
diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricConfiguration.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricConfiguration.java
new file mode 100644
index 000000000..ab00d6f89
--- /dev/null
+++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricConfiguration.java
@@ -0,0 +1,45 @@
+/*
+ * 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 Lesser 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.fabric;
+
+import com.sk89q.worldedit.util.PropertiesConfiguration;
+
+import java.io.File;
+
+public class FabricConfiguration extends PropertiesConfiguration {
+
+ public boolean creativeEnable = false;
+ public boolean cheatMode = false;
+
+ public FabricConfiguration(FabricWorldEdit mod) {
+ super(new File(mod.getWorkingDir(), "worldedit.properties"));
+ }
+
+ @Override
+ protected void loadExtra() {
+ creativeEnable = getBool("use-in-creative", false);
+ cheatMode = getBool("cheat-mode", false);
+ }
+
+ @Override
+ public File getWorkingDirectory() {
+ return FabricWorldEdit.inst.getWorkingDir();
+ }
+}
\ No newline at end of file
diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricDataFixer.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricDataFixer.java
new file mode 100644
index 000000000..c04e06840
--- /dev/null
+++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricDataFixer.java
@@ -0,0 +1,2728 @@
+/*
+ * 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 Lesser 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.fabric;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+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.DataFixerBuilder;
+import com.mojang.datafixers.Dynamic;
+import com.mojang.datafixers.schemas.Schema;
+import com.sk89q.jnbt.CompoundTag;
+import net.minecraft.datafixers.NbtOps;
+import net.minecraft.datafixers.Schemas;
+import net.minecraft.datafixers.TypeReferences;
+import net.minecraft.nbt.FloatTag;
+import net.minecraft.nbt.ListTag;
+import net.minecraft.nbt.StringTag;
+import net.minecraft.nbt.Tag;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.chat.TextComponent;
+import net.minecraft.util.DyeColor;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.JsonHelper;
+import net.minecraft.util.math.Direction;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+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.Random;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.Executor;
+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")
+class FabricDataFixer extends DataFixerBuilder 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((CompoundTag) original, srcVer);
+ } else if (type == FixTypes.BLOCK_ENTITY) {
+ return (T) fixBlockEntity((CompoundTag) original, srcVer);
+ } else if (type == FixTypes.ENTITY) {
+ return (T) fixEntity((CompoundTag) 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 CompoundTag fixChunk(CompoundTag originalChunk, int srcVer) {
+ net.minecraft.nbt.CompoundTag tag = NBTConverter.toNative(originalChunk);
+ net.minecraft.nbt.CompoundTag fixed = convert(LegacyType.CHUNK, tag, srcVer);
+ return NBTConverter.fromNative(fixed);
+ }
+
+ private CompoundTag fixBlockEntity(CompoundTag origTileEnt, int srcVer) {
+ net.minecraft.nbt.CompoundTag tag = NBTConverter.toNative(origTileEnt);
+ net.minecraft.nbt.CompoundTag fixed = convert(LegacyType.BLOCK_ENTITY, tag, srcVer);
+ return NBTConverter.fromNative(fixed);
+ }
+
+ private CompoundTag fixEntity(CompoundTag origEnt, int srcVer) {
+ net.minecraft.nbt.CompoundTag tag = NBTConverter.toNative(origEnt);
+ net.minecraft.nbt.CompoundTag fixed = convert(LegacyType.ENTITY, tag, srcVer);
+ return NBTConverter.fromNative(fixed);
+ }
+
+ private String fixBlockState(String blockState, int srcVer) {
+ net.minecraft.nbt.CompoundTag stateNBT = stateToNBT(blockState);
+ Dynamic dynamic = new Dynamic<>(OPS_NBT, stateNBT);
+ net.minecraft.nbt.CompoundTag fixed = (net.minecraft.nbt.CompoundTag) INSTANCE.fixer.update(TypeReferences.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"));
+ if (tagCompound.containsKey("Properties", 10)) {
+ sb.append('[');
+ net.minecraft.nbt.CompoundTag props = tagCompound.getCompound("Properties");
+ sb.append(props.getKeys().stream().map(k -> k + "=" + props.getString(k).replace("\"", "")).collect(Collectors.joining(",")));
+ sb.append(']');
+ }
+ return sb.toString();
+ }
+
+ private static net.minecraft.nbt.CompoundTag stateToNBT(String blockState) {
+ int propIdx = blockState.indexOf('[');
+ net.minecraft.nbt.CompoundTag tag = new net.minecraft.nbt.CompoundTag();
+ if (propIdx < 0) {
+ tag.putString("Name", blockState);
+ } else {
+ tag.putString("Name", blockState.substring(0, propIdx));
+ net.minecraft.nbt.CompoundTag propTag = new net.minecraft.nbt.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, TypeReferences.BIOME);
+ }
+
+ private String fixItemType(String key, int srcVer) {
+ return fixName(key, srcVer, TypeReferences.ITEM_NAME);
+ }
+
+ private static String fixName(String key, int srcVer, TypeReference type) {
+ return INSTANCE.fixer.update(type, new Dynamic<>(OPS_NBT, new StringTag(key)), srcVer, DATA_VERSION)
+ .asString().orElse(key);
+ }
+
+ private static final NbtOps OPS_NBT = NbtOps.INSTANCE;
+ private static final int LEGACY_VERSION = 1343;
+ private static int DATA_VERSION;
+ private static FabricDataFixer 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(TypeReferences.LEVEL),
+ PLAYER(TypeReferences.PLAYER),
+ CHUNK(TypeReferences.CHUNK),
+ BLOCK_ENTITY(TypeReferences.BLOCK_ENTITY),
+ ENTITY(TypeReferences.ENTITY),
+ ITEM_INSTANCE(TypeReferences.ITEM_STACK),
+ OPTIONS(TypeReferences.OPTIONS),
+ STRUCTURE(TypeReferences.STRUCTURE);
+
+ private final TypeReference type;
+
+ LegacyType(TypeReference type) {
+ this.type = type;
+ DFU_TO_LEGACY.put(type.typeName(), this);
+ }
+
+ public TypeReference getDFUType() {
+ return type;
+ }
+ }
+
+ FabricDataFixer(int dataVersion) {
+ super(dataVersion);
+ DATA_VERSION = dataVersion;
+ INSTANCE = this;
+ registerConverters();
+ registerInspectors();
+ this.fixer = new WrappedDataFixer(Schemas.getFixer());
+ }
+
+ @Override
+ public DataFixer build(final Executor executor) {
+ return fixer;
+ }
+
+ 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) {
+ net.minecraft.nbt.CompoundTag cmp = (net.minecraft.nbt.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 net.minecraft.nbt.CompoundTag convert(LegacyType type, net.minecraft.nbt.CompoundTag cmp, int sourceVer, int desiredVersion) {
+ List converters = FabricDataFixer.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 = FabricDataFixer.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 net.minecraft.nbt.CompoundTag convert(LegacyType type, net.minecraft.nbt.CompoundTag cmp) {
+ return convert(type.getDFUType(), cmp);
+ }
+
+ public static net.minecraft.nbt.CompoundTag convert(LegacyType type, net.minecraft.nbt.CompoundTag cmp, int sourceVer) {
+ return convert(type.getDFUType(), cmp, sourceVer);
+ }
+
+ public static net.minecraft.nbt.CompoundTag convert(LegacyType type, net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) {
+ return convert(type.getDFUType(), cmp, sourceVer, targetVer);
+ }
+
+ public static net.minecraft.nbt.CompoundTag convert(TypeReference type, net.minecraft.nbt.CompoundTag cmp) {
+ int i = cmp.containsKey("DataVersion", 99) ? cmp.getInt("DataVersion") : -1;
+ return convert(type, cmp, i);
+ }
+
+ public static net.minecraft.nbt.CompoundTag convert(TypeReference type, net.minecraft.nbt.CompoundTag cmp, int sourceVer) {
+ return convert(type, cmp, sourceVer, DATA_VERSION);
+ }
+
+ public static net.minecraft.nbt.CompoundTag convert(TypeReference type, net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) {
+ if (sourceVer >= targetVer) {
+ return cmp;
+ }
+ return (net.minecraft.nbt.CompoundTag) INSTANCE.fixer.update(type, new Dynamic<>(OPS_NBT, cmp), sourceVer, targetVer).getValue();
+ }
+
+
+ public interface DataInspector {
+ net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer);
+ }
+
+ public interface DataConverter {
+
+ int getDataVersion();
+
+ net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.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", new Identifier("item"));
+ map.put("EntityExperienceOrb", new Identifier("xp_orb"));
+ map.put("EntityAreaEffectCloud", new Identifier("area_effect_cloud"));
+ map.put("EntityGuardianElder", new Identifier("elder_guardian"));
+ map.put("EntitySkeletonWither", new Identifier("wither_skeleton"));
+ map.put("EntitySkeletonStray", new Identifier("stray"));
+ map.put("EntityEgg", new Identifier("egg"));
+ map.put("EntityLeash", new Identifier("leash_knot"));
+ map.put("EntityPainting", new Identifier("painting"));
+ map.put("EntityTippedArrow", new Identifier("arrow"));
+ map.put("EntitySnowball", new Identifier("snowball"));
+ map.put("EntityLargeFireball", new Identifier("fireball"));
+ map.put("EntitySmallFireball", new Identifier("small_fireball"));
+ map.put("EntityEnderPearl", new Identifier("ender_pearl"));
+ map.put("EntityEnderSignal", new Identifier("eye_of_ender_signal"));
+ map.put("EntityPotion", new Identifier("potion"));
+ map.put("EntityThrownExpBottle", new Identifier("xp_bottle"));
+ map.put("EntityItemFrame", new Identifier("item_frame"));
+ map.put("EntityWitherSkull", new Identifier("wither_skull"));
+ map.put("EntityTNTPrimed", new Identifier("tnt"));
+ map.put("EntityFallingBlock", new Identifier("falling_block"));
+ map.put("EntityFireworks", new Identifier("fireworks_rocket"));
+ map.put("EntityZombieHusk", new Identifier("husk"));
+ map.put("EntitySpectralArrow", new Identifier("spectral_arrow"));
+ map.put("EntityShulkerBullet", new Identifier("shulker_bullet"));
+ map.put("EntityDragonFireball", new Identifier("dragon_fireball"));
+ map.put("EntityZombieVillager", new Identifier("zombie_villager"));
+ map.put("EntityHorseSkeleton", new Identifier("skeleton_horse"));
+ map.put("EntityHorseZombie", new Identifier("zombie_horse"));
+ map.put("EntityArmorStand", new Identifier("armor_stand"));
+ map.put("EntityHorseDonkey", new Identifier("donkey"));
+ map.put("EntityHorseMule", new Identifier("mule"));
+ map.put("EntityEvokerFangs", new Identifier("evocation_fangs"));
+ map.put("EntityEvoker", new Identifier("evocation_illager"));
+ map.put("EntityVex", new Identifier("vex"));
+ map.put("EntityVindicator", new Identifier("vindication_illager"));
+ map.put("EntityIllagerIllusioner", new Identifier("illusion_illager"));
+ map.put("EntityMinecartCommandBlock", new Identifier("commandblock_minecart"));
+ map.put("EntityBoat", new Identifier("boat"));
+ map.put("EntityMinecartRideable", new Identifier("minecart"));
+ map.put("EntityMinecartChest", new Identifier("chest_minecart"));
+ map.put("EntityMinecartFurnace", new Identifier("furnace_minecart"));
+ map.put("EntityMinecartTNT", new Identifier("tnt_minecart"));
+ map.put("EntityMinecartHopper", new Identifier("hopper_minecart"));
+ map.put("EntityMinecartMobSpawner", new Identifier("spawner_minecart"));
+ map.put("EntityCreeper", new Identifier("creeper"));
+ map.put("EntitySkeleton", new Identifier("skeleton"));
+ map.put("EntitySpider", new Identifier("spider"));
+ map.put("EntityGiantZombie", new Identifier("giant"));
+ map.put("EntityZombie", new Identifier("zombie"));
+ map.put("EntitySlime", new Identifier("slime"));
+ map.put("EntityGhast", new Identifier("ghast"));
+ map.put("EntityPigZombie", new Identifier("zombie_pigman"));
+ map.put("EntityEnderman", new Identifier("enderman"));
+ map.put("EntityCaveSpider", new Identifier("cave_spider"));
+ map.put("EntitySilverfish", new Identifier("silverfish"));
+ map.put("EntityBlaze", new Identifier("blaze"));
+ map.put("EntityMagmaCube", new Identifier("magma_cube"));
+ map.put("EntityEnderDragon", new Identifier("ender_dragon"));
+ map.put("EntityWither", new Identifier("wither"));
+ map.put("EntityBat", new Identifier("bat"));
+ map.put("EntityWitch", new Identifier("witch"));
+ map.put("EntityEndermite", new Identifier("endermite"));
+ map.put("EntityGuardian", new Identifier("guardian"));
+ map.put("EntityShulker", new Identifier("shulker"));
+ map.put("EntityPig", new Identifier("pig"));
+ map.put("EntitySheep", new Identifier("sheep"));
+ map.put("EntityCow", new Identifier("cow"));
+ map.put("EntityChicken", new Identifier("chicken"));
+ map.put("EntitySquid", new Identifier("squid"));
+ map.put("EntityWolf", new Identifier("wolf"));
+ map.put("EntityMushroomCow", new Identifier("mooshroom"));
+ map.put("EntitySnowman", new Identifier("snowman"));
+ map.put("EntityOcelot", new Identifier("ocelot"));
+ map.put("EntityIronGolem", new Identifier("villager_golem"));
+ map.put("EntityHorse", new Identifier("horse"));
+ map.put("EntityRabbit", new Identifier("rabbit"));
+ map.put("EntityPolarBear", new Identifier("polar_bear"));
+ map.put("EntityLlama", new Identifier("llama"));
+ map.put("EntityLlamaSpit", new Identifier("llama_spit"));
+ map.put("EntityParrot", new Identifier("parrot"));
+ map.put("EntityVillager", new Identifier("villager"));
+ map.put("EntityEnderCrystal", new Identifier("ender_crystal"));
+ map.put("TileEntityFurnace", new Identifier("furnace"));
+ map.put("TileEntityChest", new Identifier("chest"));
+ map.put("TileEntityEnderChest", new Identifier("ender_chest"));
+ map.put("TileEntityRecordPlayer", new Identifier("jukebox"));
+ map.put("TileEntityDispenser", new Identifier("dispenser"));
+ map.put("TileEntityDropper", new Identifier("dropper"));
+ map.put("TileEntitySign", new Identifier("sign"));
+ map.put("TileEntityMobSpawner", new Identifier("mob_spawner"));
+ map.put("TileEntityNote", new Identifier("noteblock"));
+ map.put("TileEntityPiston", new Identifier("piston"));
+ map.put("TileEntityBrewingStand", new Identifier("brewing_stand"));
+ map.put("TileEntityEnchantTable", new Identifier("enchanting_table"));
+ map.put("TileEntityEnderPortal", new Identifier("end_portal"));
+ map.put("TileEntityBeacon", new Identifier("beacon"));
+ map.put("TileEntitySkull", new Identifier("skull"));
+ map.put("TileEntityLightDetector", new Identifier("daylight_detector"));
+ map.put("TileEntityHopper", new Identifier("hopper"));
+ map.put("TileEntityComparator", new Identifier("comparator"));
+ map.put("TileEntityFlowerPot", new Identifier("flower_pot"));
+ map.put("TileEntityBanner", new Identifier("banner"));
+ map.put("TileEntityStructure", new Identifier("structure_block"));
+ map.put("TileEntityEndGateway", new Identifier("end_gateway"));
+ map.put("TileEntityCommand", new Identifier("command_block"));
+ map.put("TileEntityShulkerBox", new Identifier("shulker_box"));
+ map.put("TileEntityBed", new Identifier("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.getCompound(key), sourceVer, targetVer));
+ }
+
+ private static void convertItem(net.minecraft.nbt.CompoundTag nbttagcompound, String key, int sourceVer, int targetVer) {
+ if (nbttagcompound.containsKey(key, 10)) {
+ convertCompound(LegacyType.ITEM_INSTANCE, nbttagcompound, key, sourceVer, targetVer);
+ }
+ }
+
+ private static void convertItems(net.minecraft.nbt.CompoundTag nbttagcompound, String key, int sourceVer, int targetVer) {
+ if (nbttagcompound.containsKey(key, 9)) {
+ ListTag nbttaglist = nbttagcompound.getList(key, 10);
+
+ for (int j = 0; j < nbttaglist.size(); ++j) {
+ nbttaglist.add(j, convert(LegacyType.ITEM_INSTANCE, nbttaglist.getCompoundTag(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.getList("Equipment", 10);
+ ListTag nbttaglist1;
+
+ if (!nbttaglist.isEmpty() && !cmp.containsKey("HandItems", 10)) {
+ nbttaglist1 = new ListTag();
+ nbttaglist1.add(nbttaglist.get(0));
+ nbttaglist1.add(new net.minecraft.nbt.CompoundTag());
+ cmp.put("HandItems", nbttaglist1);
+ }
+
+ if (nbttaglist.size() > 1 && !cmp.containsKey("ArmorItem", 10)) {
+ 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");
+ if (cmp.containsKey("DropChances", 9)) {
+ nbttaglist1 = cmp.getList("DropChances", 5);
+ ListTag nbttaglist2;
+
+ if (!cmp.containsKey("HandDropChances", 10)) {
+ nbttaglist2 = new ListTag();
+ nbttaglist2.add(new FloatTag(nbttaglist1.getFloat(0)));
+ nbttaglist2.add(new FloatTag(0.0F));
+ cmp.put("HandDropChances", nbttaglist2);
+ }
+
+ if (!cmp.containsKey("ArmorDropChances", 10)) {
+ nbttaglist2 = new ListTag();
+ nbttaglist2.add(new FloatTag(nbttaglist1.getFloat(1)));
+ nbttaglist2.add(new FloatTag(nbttaglist1.getFloat(2)));
+ nbttaglist2.add(new FloatTag(nbttaglist1.getFloat(3)));
+ nbttaglist2.add(new FloatTag(nbttaglist1.getFloat(4)));
+ 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 = new Identifier(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) {
+ if (!cmp.containsKey("tag", 10)) {
+ return cmp;
+ } else {
+ net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("tag");
+
+ if (nbttagcompound1.containsKey("BlockEntityTag", 10)) {
+ net.minecraft.nbt.CompoundTag nbttagcompound2 = nbttagcompound1.getCompound("BlockEntityTag");
+ String s = cmp.getString("id");
+ 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.containsKey("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(FabricDataFixer.class);
+
+ DataInspectorEntity() {}
+
+ @Override
+ public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) {
+ net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("tag");
+
+ if (nbttagcompound1.containsKey("EntityTag", 10)) {
+ net.minecraft.nbt.CompoundTag nbttagcompound2 = nbttagcompound1.getCompound("EntityTag");
+ String s = cmp.getString("id");
+ String s1;
+
+ if ("minecraft:armor_stand".equals(s)) {
+ s1 = sourceVer < 515 ? "ArmorStand" : "minecraft:armor_stand";
+ } else {
+ if (!"minecraft:spawn_egg".equals(s)) {
+ return cmp;
+ }
+
+ s1 = nbttagcompound2.getString("id");
+ }
+
+ boolean flag;
+
+ if (s1 == null) {
+ DataInspectorEntity.a.warn("Unable to resolve Entity for ItemInstance: {}", s);
+ flag = false;
+ } else {
+ flag = !nbttagcompound2.containsKey("id", 8);
+ 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 (this.key.equals(new Identifier(cmp.getString("id")))) {
+ 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) {
+ FabricDataFixer.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) {
+ FabricDataFixer.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) {
+ if (cmp.containsKey("id", 99)) {
+ short short0 = cmp.getShort("id");
+
+ 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";
+ }
+ }
+
+ 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")) && cmp.getBoolean("Silent") && !cmp.getBoolean("Marker")) {
+ 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")) && cmp.containsKey("tag", 10)) {
+ net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("tag");
+
+ if (nbttagcompound1.containsKey("BlockEntityTag", 10)) {
+ net.minecraft.nbt.CompoundTag nbttagcompound2 = nbttagcompound1.getCompound("BlockEntityTag");
+
+ if (nbttagcompound2.containsKey("Base", 99)) {
+ cmp.putShort("Damage", (short) (nbttagcompound2.getShort("Base") & 15));
+ if (nbttagcompound1.containsKey("display", 10)) {
+ net.minecraft.nbt.CompoundTag nbttagcompound3 = nbttagcompound1.getCompound("display");
+
+ if (nbttagcompound3.containsKey("Lore", 9)) {
+ ListTag nbttaglist = nbttagcompound3.getList("Lore", 8);
+
+ if (nbttaglist.size() == 1 && "(+NBT)".equals(nbttaglist.getString(0))) {
+ return cmp;
+ }
+ }
+ }
+
+ 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"))) {
+ net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("tag");
+ short short0 = cmp.getShort("Damage");
+
+ if (!nbttagcompound1.containsKey("Potion", 8)) {
+ 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"))) {
+ net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("tag");
+ net.minecraft.nbt.CompoundTag nbttagcompound2 = nbttagcompound1.getCompound("EntityTag");
+ short short0 = cmp.getShort("Damage");
+
+ if (!nbttagcompound2.containsKey("id", 8)) {
+ 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 = Lists.newArrayList(new String[] { "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"))) {
+ String s = "MinecartRideable";
+ int i = cmp.getInt("Type");
+
+ 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"))) {
+ return cmp;
+ } else {
+ if (cmp.containsKey("EntityId", 8)) {
+ String s = cmp.getString("EntityId");
+ net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("SpawnData");
+
+ nbttagcompound1.putString("id", s.isEmpty() ? "Pig" : s);
+ cmp.put("SpawnData", nbttagcompound1);
+ cmp.remove("EntityId");
+ }
+
+ if (cmp.containsKey("SpawnPotentials", 9)) {
+ ListTag nbttaglist = cmp.getList("SpawnPotentials", 10);
+
+ for (int i = 0; i < nbttaglist.size(); ++i) {
+ net.minecraft.nbt.CompoundTag nbttagcompound2 = nbttaglist.getCompoundTag(i);
+
+ if (nbttagcompound2.containsKey("Type", 8)) {
+ net.minecraft.nbt.CompoundTag nbttagcompound3 = nbttagcompound2.getCompound("Properties");
+
+ nbttagcompound3.putString("id", nbttagcompound2.getString("Type"));
+ 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) {
+ if (cmp.containsKey("UUID", 8)) {
+ cmp.putUuid("UUID", UUID.fromString(cmp.getString("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"))) {
+ float f;
+
+ if (cmp.containsKey("HealF", 99)) {
+ f = cmp.getFloat("HealF");
+ cmp.remove("HealF");
+ } else {
+ if (!cmp.containsKey("Health", 99)) {
+ return cmp;
+ }
+
+ f = cmp.getFloat("Health");
+ }
+
+ 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")) && !cmp.containsKey("SaddleItem", 10) && cmp.getBoolean("Saddle")) {
+ 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");
+ boolean flag = "Painting".equals(s);
+ boolean flag1 = "ItemFrame".equals(s);
+
+ if ((flag || flag1) && !cmp.containsKey("Facing", 99)) {
+ Direction enumdirection;
+
+ if (cmp.containsKey("Direction", 99)) {
+ enumdirection = Direction.fromHorizontal(cmp.getByte("Direction"));
+ cmp.putInt("TileX", cmp.getInt("TileX") + enumdirection.getOffsetX());
+ cmp.putInt("TileY", cmp.getInt("TileY") + enumdirection.getOffsetY());
+ cmp.putInt("TileZ", cmp.getInt("TileZ") + enumdirection.getOffsetZ());
+ cmp.remove("Direction");
+ if (flag1 && cmp.containsKey("ItemRotation", 99)) {
+ cmp.putByte("ItemRotation", (byte) (cmp.getByte("ItemRotation") * 2));
+ }
+ } else {
+ enumdirection = Direction.fromHorizontal(cmp.getByte("Dir"));
+ cmp.remove("Dir");
+ }
+
+ cmp.putByte("Facing", (byte) enumdirection.getHorizontal());
+ }
+
+ 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) {
+ ListTag nbttaglist;
+
+ if (cmp.containsKey("HandDropChances", 9)) {
+ nbttaglist = cmp.getList("HandDropChances", 5);
+ if (nbttaglist.size() == 2 && nbttaglist.getFloat(0) == 0.0F && nbttaglist.getFloat(1) == 0.0F) {
+ cmp.remove("HandDropChances");
+ }
+ }
+
+ if (cmp.containsKey("ArmorDropChances", 9)) {
+ nbttaglist = cmp.getList("ArmorDropChances", 5);
+ if (nbttaglist.size() == 4 && nbttaglist.getFloat(0) == 0.0F && nbttaglist.getFloat(1) == 0.0F && nbttaglist.getFloat(2) == 0.0F && nbttaglist.getFloat(3) == 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.containsKey("Riding", 10)) {
+ 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.getCompound("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"))) {
+ net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("tag");
+
+ if (nbttagcompound1.containsKey("pages", 9)) {
+ ListTag nbttaglist = nbttagcompound1.getList("pages", 8);
+
+ for (int i = 0; i < nbttaglist.size(); ++i) {
+ String s = nbttaglist.getString(i);
+ 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 = new TextComponent(s);
+ } else {
+ try {
+ object = JsonHelper.deserialize(DataConverterSignText.a, s, Component.class, true);
+ if (object == null) {
+ object = new TextComponent("");
+ }
+ } catch (JsonParseException jsonparseexception) {
+ ;
+ }
+
+ if (object == null) {
+ try {
+ object = Component.Serializer.fromJsonString(s);
+ } catch (JsonParseException jsonparseexception1) {
+ ;
+ }
+ }
+
+ if (object == null) {
+ try {
+ object = Component.Serializer.fromLenientJsonString(s);
+ } catch (JsonParseException jsonparseexception2) {
+ ;
+ }
+ }
+
+ if (object == null) {
+ object = new TextComponent(s);
+ }
+ }
+ } else {
+ object = new TextComponent("");
+ }
+
+ nbttaglist.set(i, new StringTag(Component.Serializer.toJsonString((Component) object)));
+ }
+
+ nbttagcompound1.put("pages", nbttaglist);
+ }
+ }
+
+ return cmp;
+ }
+ }
+
+ private static class DataConverterCookedFish implements DataConverter {
+
+ private static final Identifier a = new Identifier("cooked_fished");
+
+ DataConverterCookedFish() {}
+
+ public int getDataVersion() {
+ return 502;
+ }
+
+ public net.minecraft.nbt.CompoundTag convert(net.minecraft.nbt.CompoundTag cmp) {
+ if (cmp.containsKey("id", 8) && DataConverterCookedFish.a.equals(new Identifier(cmp.getString("id")))) {
+ 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")) && cmp.getBoolean("IsVillager")) {
+ if (!cmp.containsKey("ZombieType", 99)) {
+ int i = -1;
+
+ if (cmp.containsKey("VillagerProfession", 99)) {
+ try {
+ i = this.convert(cmp.getInt("VillagerProfession"));
+ } catch (RuntimeException runtimeexception) {
+ ;
+ }
+ }
+
+ 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"))) {
+ if (cmp.getBoolean("Elder")) {
+ 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");
+
+ if ("Skeleton".equals(s)) {
+ int i = cmp.getInt("SkeletonType");
+
+ 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"))) {
+ int i = cmp.getInt("ZombieType");
+
+ 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"))) {
+ int i = cmp.getInt("Type");
+
+ 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"));
+
+ 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"));
+
+ 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");
+
+ 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.getCompound("tag");
+
+ if (!nbttagcompound1.containsKey("Potion", 8)) {
+ nbttagcompound1.putString("Potion", "minecraft:water");
+ }
+
+ if (!cmp.containsKey("tag", 10)) {
+ 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")) && !cmp.containsKey("Color", 99)) {
+ 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")) && cmp.containsKey("tag", 10)) {
+ net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("tag");
+
+ if (nbttagcompound1.containsKey("BlockEntityTag", 10)) {
+ net.minecraft.nbt.CompoundTag nbttagcompound2 = nbttagcompound1.getCompound("BlockEntityTag");
+
+ if (nbttagcompound2.getList("Items", 10).isEmpty()) {
+ nbttagcompound2.remove("Items");
+ }
+
+ int i = nbttagcompound2.getInt("Color");
+
+ 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"))) {
+ 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) {
+ if (cmp.containsKey("lang", 8)) {
+ cmp.putString("lang", cmp.getString("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"))) {
+ cmp.putString("id", "minecraft:totem_of_undying");
+ }
+
+ return cmp;
+ }
+ }
+
+ private static class DataConverterBedBlock implements DataConverter {
+
+ private static final Logger a = LogManager.getLogger(FabricDataFixer.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.getCompound("Level");
+ int i = nbttagcompound1.getInt("xPos");
+ int j = nbttagcompound1.getInt("zPos");
+ ListTag nbttaglist = nbttagcompound1.getList("TileEntities", 10);
+ ListTag nbttaglist1 = nbttagcompound1.getList("Sections", 10);
+
+ for (int k = 0; k < nbttaglist1.size(); ++k) {
+ net.minecraft.nbt.CompoundTag nbttagcompound2 = nbttaglist1.getCompoundTag(k);
+ byte b0 = nbttagcompound2.getByte("Y");
+ byte[] abyte = nbttagcompound2.getByteArray("Blocks");
+
+ 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")) && cmp.getShort("Damage") == 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() {
+ Component a(JsonElement jsonelement, Type type, JsonDeserializationContext jsondeserializationcontext) throws JsonParseException {
+ if (jsonelement.isJsonPrimitive()) {
+ return new TextComponent(jsonelement.getAsString());
+ } else if (jsonelement.isJsonArray()) {
+ JsonArray jsonarray = jsonelement.getAsJsonArray();
+ Component iTextComponent = null;
+ Iterator iterator = jsonarray.iterator();
+
+ while (iterator.hasNext()) {
+ JsonElement jsonelement1 = (JsonElement) iterator.next();
+ Component 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"))) {
+ 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);
+ 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 = new TextComponent(s1);
+ } else {
+ try {
+ object = JsonHelper.deserialize(DataConverterSignText.a, s1, Component.class, true);
+ if (object == null) {
+ object = new TextComponent("");
+ }
+ } catch (JsonParseException jsonparseexception) {
+ ;
+ }
+
+ if (object == null) {
+ try {
+ object = Component.Serializer.fromJsonString(s1);
+ } catch (JsonParseException jsonparseexception1) {
+ ;
+ }
+ }
+
+ if (object == null) {
+ try {
+ object = Component.Serializer.fromLenientJsonString(s1);
+ } catch (JsonParseException jsonparseexception2) {
+ ;
+ }
+ }
+
+ if (object == null) {
+ object = new TextComponent(s1);
+ }
+ }
+ } else {
+ object = new TextComponent("");
+ }
+
+ nbttagcompound.putString(s, Component.Serializer.toJsonString((Component) object));
+ }
+ }
+
+ private static class DataInspectorPlayerVehicle implements DataInspector {
+ @Override
+ public net.minecraft.nbt.CompoundTag inspect(net.minecraft.nbt.CompoundTag cmp, int sourceVer, int targetVer) {
+ if (cmp.containsKey("RootVehicle", 10)) {
+ net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("RootVehicle");
+
+ if (nbttagcompound1.containsKey("Entity", 10)) {
+ 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.containsKey("Player", 10)) {
+ 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) {
+ ListTag nbttaglist;
+ int j;
+ net.minecraft.nbt.CompoundTag nbttagcompound1;
+
+ if (cmp.containsKey("entities", 9)) {
+ nbttaglist = cmp.getList("entities", 10);
+
+ for (j = 0; j < nbttaglist.size(); ++j) {
+ nbttagcompound1 = (net.minecraft.nbt.CompoundTag) nbttaglist.get(j);
+ if (nbttagcompound1.containsKey("nbt", 10)) {
+ convertCompound(LegacyType.ENTITY, nbttagcompound1, "nbt", sourceVer, targetVer);
+ }
+ }
+ }
+
+ if (cmp.containsKey("blocks", 9)) {
+ nbttaglist = cmp.getList("blocks", 10);
+
+ for (j = 0; j < nbttaglist.size(); ++j) {
+ nbttagcompound1 = (net.minecraft.nbt.CompoundTag) nbttaglist.get(j);
+ if (nbttagcompound1.containsKey("nbt", 10)) {
+ 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) {
+ if (cmp.containsKey("Level", 10)) {
+ net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("Level");
+ ListTag nbttaglist;
+ int j;
+
+ if (nbttagcompound1.containsKey("Entities", 9)) {
+ nbttaglist = nbttagcompound1.getList("Entities", 10);
+
+ for (j = 0; j < nbttaglist.size(); ++j) {
+ nbttaglist.set(j, convert(LegacyType.ENTITY, (net.minecraft.nbt.CompoundTag) nbttaglist.get(j), sourceVer, targetVer));
+ }
+ }
+
+ if (nbttagcompound1.containsKey("TileEntities", 9)) {
+ nbttaglist = nbttagcompound1.getList("TileEntities", 10);
+
+ for (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) {
+ if (cmp.containsKey("Passengers", 9)) {
+ ListTag nbttaglist = cmp.getList("Passengers", 10);
+
+ for (int j = 0; j < nbttaglist.size(); ++j) {
+ nbttaglist.set(j, convert(LegacyType.ENTITY, nbttaglist.getCompoundTag(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.containsKey("ShoulderEntityLeft", 10)) {
+ convertCompound(LegacyType.ENTITY, cmp, "ShoulderEntityLeft", sourceVer, targetVer);
+ }
+
+ if (cmp.containsKey("ShoulderEntityRight", 10)) {
+ 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 (entityVillager.equals(new Identifier(cmp.getString("id"))) && cmp.containsKey("Offers", 10)) {
+ net.minecraft.nbt.CompoundTag nbttagcompound1 = cmp.getCompound("Offers");
+
+ if (nbttagcompound1.containsKey("Recipes", 9)) {
+ ListTag nbttaglist = nbttagcompound1.getList("Recipes", 10);
+
+ for (int j = 0; j < nbttaglist.size(); ++j) {
+ net.minecraft.nbt.CompoundTag nbttagcompound2 = nbttaglist.getCompoundTag(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");
+ if (entityMinecartMobSpawner.equals(new Identifier(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 (tileEntityMobSpawner.equals(new Identifier(cmp.getString("id")))) {
+ if (cmp.containsKey("SpawnPotentials", 9)) {
+ ListTag nbttaglist = cmp.getList("SpawnPotentials", 10);
+
+ for (int j = 0; j < nbttaglist.size(); ++j) {
+ net.minecraft.nbt.CompoundTag nbttagcompound1 = nbttaglist.getCompoundTag(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 (tileEntityCommand.equals(new Identifier(cmp.getString("id")))) {
+ cmp.putString("id", "Control");
+ convert(LegacyType.BLOCK_ENTITY, cmp, sourceVer, targetVer);
+ cmp.putString("id", "MinecartCommandBlock");
+ }
+
+ return cmp;
+ }
+ }
+}
diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricEntity.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricEntity.java
new file mode 100644
index 000000000..d1fb75a04
--- /dev/null
+++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricEntity.java
@@ -0,0 +1,116 @@
+/*
+ * 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 Lesser 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.fabric;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.sk89q.worldedit.entity.BaseEntity;
+import com.sk89q.worldedit.entity.Entity;
+import com.sk89q.worldedit.entity.metadata.EntityProperties;
+import com.sk89q.worldedit.extent.Extent;
+import com.sk89q.worldedit.math.Vector3;
+import com.sk89q.worldedit.util.Location;
+import com.sk89q.worldedit.world.NullWorld;
+import com.sk89q.worldedit.world.entity.EntityTypes;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.registry.Registry;
+
+import java.lang.ref.WeakReference;
+
+import javax.annotation.Nullable;
+
+class FabricEntity implements Entity {
+
+ private final WeakReference entityRef;
+
+ FabricEntity(net.minecraft.entity.Entity entity) {
+ checkNotNull(entity);
+ this.entityRef = new WeakReference<>(entity);
+ }
+
+ @Override
+ public BaseEntity getState() {
+ net.minecraft.entity.Entity entity = entityRef.get();
+ if (entity != null) {
+ Identifier id = Registry.ENTITY_TYPE.getId(entity.getType());
+ CompoundTag tag = new CompoundTag();
+ entity.toTag(tag);
+ return new BaseEntity(EntityTypes.get(id.toString()), NBTConverter.fromNative(tag));
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public Location getLocation() {
+ net.minecraft.entity.Entity entity = entityRef.get();
+ if (entity != null) {
+ Vector3 position = Vector3.at(entity.x, entity.y, entity.z);
+ float yaw = entity.yaw;
+ float pitch = entity.pitch;
+
+ return new Location(FabricAdapter.adapt(entity.world), position, yaw, pitch);
+ } else {
+ return new Location(NullWorld.getInstance());
+ }
+ }
+
+ @Override
+ public boolean setLocation(Location location) {
+ // TODO unused atm
+ return false;
+ }
+
+ @Override
+ public Extent getExtent() {
+ net.minecraft.entity.Entity entity = entityRef.get();
+ if (entity != null) {
+ return FabricAdapter.adapt(entity.world);
+ } else {
+ return NullWorld.getInstance();
+ }
+ }
+
+ @Override
+ public boolean remove() {
+ net.minecraft.entity.Entity entity = entityRef.get();
+ if (entity != null) {
+ entity.remove();
+ }
+ return true;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Nullable
+ @Override
+ public T getFacet(Class extends T> cls) {
+ net.minecraft.entity.Entity entity = entityRef.get();
+ if (entity != null) {
+ if (EntityProperties.class.isAssignableFrom(cls)) {
+ return (T) new FabricEntityProperties(entity);
+ } else {
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricEntityProperties.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricEntityProperties.java
new file mode 100644
index 000000000..eafbe5c94
--- /dev/null
+++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricEntityProperties.java
@@ -0,0 +1,151 @@
+/*
+ * 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 Lesser 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.fabric;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.sk89q.worldedit.entity.metadata.EntityProperties;
+import net.minecraft.entity.EnderEyeEntity;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.ExperienceOrbEntity;
+import net.minecraft.entity.FallingBlockEntity;
+import net.minecraft.entity.ItemEntity;
+import net.minecraft.entity.Npc;
+import net.minecraft.entity.TntEntity;
+import net.minecraft.entity.boss.dragon.EnderDragonEntity;
+import net.minecraft.entity.decoration.ArmorStandEntity;
+import net.minecraft.entity.decoration.ItemFrameEntity;
+import net.minecraft.entity.decoration.painting.PaintingEntity;
+import net.minecraft.entity.mob.AmbientEntity;
+import net.minecraft.entity.mob.MobEntity;
+import net.minecraft.entity.passive.AnimalEntity;
+import net.minecraft.entity.passive.TameableEntity;
+import net.minecraft.entity.passive.GolemEntity;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.entity.projectile.Projectile;
+import net.minecraft.entity.vehicle.AbstractMinecartEntity;
+import net.minecraft.entity.vehicle.BoatEntity;
+import net.minecraft.server.network.ServerPlayerEntity;
+import net.minecraft.village.Trader;
+
+public class FabricEntityProperties implements EntityProperties {
+
+ private final Entity entity;
+
+ public FabricEntityProperties(Entity entity) {
+ checkNotNull(entity);
+ this.entity = entity;
+ }
+
+ @Override
+ public boolean isPlayerDerived() {
+ return entity instanceof PlayerEntity;
+ }
+
+ @Override
+ public boolean isProjectile() {
+ return entity instanceof EnderEyeEntity || entity instanceof Projectile;
+ }
+
+ @Override
+ public boolean isItem() {
+ return entity instanceof ItemEntity;
+ }
+
+ @Override
+ public boolean isFallingBlock() {
+ return entity instanceof FallingBlockEntity;
+ }
+
+ @Override
+ public boolean isPainting() {
+ return entity instanceof PaintingEntity;
+ }
+
+ @Override
+ public boolean isItemFrame() {
+ return entity instanceof ItemFrameEntity;
+ }
+
+ @Override
+ public boolean isBoat() {
+ return entity instanceof BoatEntity;
+ }
+
+ @Override
+ public boolean isMinecart() {
+ return entity instanceof AbstractMinecartEntity;
+ }
+
+ @Override
+ public boolean isTNT() {
+ return entity instanceof TntEntity;
+ }
+
+ @Override
+ public boolean isExperienceOrb() {
+ return entity instanceof ExperienceOrbEntity;
+ }
+
+ @Override
+ public boolean isLiving() {
+ return entity instanceof MobEntity;
+ }
+
+ @Override
+ public boolean isAnimal() {
+ return entity instanceof AnimalEntity;
+ }
+
+ @Override
+ public boolean isAmbient() {
+ return entity instanceof AmbientEntity;
+ }
+
+ @Override
+ public boolean isNPC() {
+ return entity instanceof Npc || entity instanceof Trader;
+ }
+
+ @Override
+ public boolean isGolem() {
+ return entity instanceof GolemEntity;
+ }
+
+ @Override
+ public boolean isTamed() {
+ return entity instanceof TameableEntity && ((TameableEntity) entity).isTamed();
+ }
+
+ @Override
+ public boolean isTagged() {
+ return entity.hasCustomName();
+ }
+
+ @Override
+ public boolean isArmorStand() {
+ return entity instanceof ArmorStandEntity;
+ }
+
+ @Override
+ public boolean isPasteable() {
+ return !(entity instanceof ServerPlayerEntity || entity instanceof EnderDragonEntity);
+ }
+}
diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricItemCategoryRegistry.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricItemCategoryRegistry.java
new file mode 100644
index 000000000..e8ec3e43f
--- /dev/null
+++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricItemCategoryRegistry.java
@@ -0,0 +1,46 @@
+/*
+ * 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 Lesser 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.fabric;
+
+import com.sk89q.worldedit.registry.Category;
+import com.sk89q.worldedit.world.item.ItemType;
+import com.sk89q.worldedit.world.registry.ItemCategoryRegistry;
+import net.minecraft.tag.ItemTags;
+import net.minecraft.tag.Tag;
+import net.minecraft.util.Identifier;
+
+import java.util.Collections;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class FabricItemCategoryRegistry implements ItemCategoryRegistry {
+ @Override
+ public Set getCategorisedByName(String category) {
+ return Optional.ofNullable(ItemTags.getContainer().get(new Identifier(category)))
+ .map(Tag::values).orElse(Collections.emptySet())
+ .stream().map(FabricAdapter::adapt).collect(Collectors.toSet());
+ }
+
+ @Override
+ public Set getAll(Category category) {
+ return getCategorisedByName(category.getId());
+ }
+}
diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricItemRegistry.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricItemRegistry.java
new file mode 100644
index 000000000..e3497234f
--- /dev/null
+++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricItemRegistry.java
@@ -0,0 +1,42 @@
+/*
+ * 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 Lesser 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.fabric;
+
+import com.sk89q.worldedit.world.item.ItemType;
+import com.sk89q.worldedit.world.registry.BundledItemRegistry;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.loader.api.FabricLoader;
+import net.minecraft.client.resource.language.I18n;
+import net.minecraft.item.Item;
+
+import javax.annotation.Nullable;
+
+public class FabricItemRegistry extends BundledItemRegistry {
+
+ @Nullable
+ @Override
+ public String getName(ItemType itemType) {
+ if (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) {
+ final Item item = FabricAdapter.adapt(itemType);
+ return I18n.translate(item.getTranslationKey());
+ }
+ return super.getName(itemType);
+ }
+}
diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricPermissionsProvider.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricPermissionsProvider.java
new file mode 100644
index 000000000..68ff54bef
--- /dev/null
+++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricPermissionsProvider.java
@@ -0,0 +1,50 @@
+/*
+ * 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 Lesser 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.fabric;
+
+import net.minecraft.server.network.ServerPlayerEntity;
+import net.minecraft.world.GameMode;
+
+public interface FabricPermissionsProvider {
+
+ boolean hasPermission(ServerPlayerEntity player, String permission);
+
+ void registerPermission(String permission);
+
+ class VanillaPermissionsProvider implements FabricPermissionsProvider {
+
+ private FabricPlatform platform;
+
+ public VanillaPermissionsProvider(FabricPlatform platform) {
+ this.platform = platform;
+ }
+
+ @Override
+ public boolean hasPermission(ServerPlayerEntity player, String permission) {
+ FabricConfiguration configuration = platform.getConfiguration();
+ return configuration.cheatMode ||
+ player.server.getPlayerManager().isOperator(player.getGameProfile()) ||
+ (configuration.creativeEnable && player.interactionManager.getGameMode() == GameMode.CREATIVE);
+ }
+
+ @Override
+ public void registerPermission(String permission) {}
+ }
+}
diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricPlatform.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricPlatform.java
new file mode 100644
index 000000000..69ea094b2
--- /dev/null
+++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricPlatform.java
@@ -0,0 +1,202 @@
+/*
+ * 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 Lesser 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.fabric;
+
+import com.sk89q.worldedit.command.util.PermissionCondition;
+import com.sk89q.worldedit.entity.Player;
+import com.sk89q.worldedit.extension.platform.AbstractPlatform;
+import com.sk89q.worldedit.extension.platform.Actor;
+import com.sk89q.worldedit.extension.platform.Capability;
+import com.sk89q.worldedit.extension.platform.MultiUserPlatform;
+import com.sk89q.worldedit.extension.platform.Preference;
+import com.sk89q.worldedit.world.DataFixer;
+import com.sk89q.worldedit.world.World;
+import com.sk89q.worldedit.world.registry.Registries;
+import net.minecraft.SharedConstants;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.PlayerManager;
+import net.minecraft.server.network.ServerPlayerEntity;
+import net.minecraft.server.world.ServerWorld;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.registry.Registry;
+import org.enginehub.piston.Command;
+import org.enginehub.piston.CommandManager;
+
+import javax.annotation.Nullable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static java.util.stream.Collectors.toList;
+
+class FabricPlatform extends AbstractPlatform implements MultiUserPlatform {
+
+ private final FabricWorldEdit mod;
+ private final MinecraftServer server;
+ private final FabricDataFixer dataFixer;
+ private boolean hookingEvents = false;
+
+ FabricPlatform(FabricWorldEdit mod, MinecraftServer server) {
+ this.mod = mod;
+ this.server = server;
+ this.dataFixer = new FabricDataFixer(getDataVersion());
+ }
+
+ boolean isHookingEvents() {
+ return hookingEvents;
+ }
+
+ @Override
+ public Registries getRegistries() {
+ return FabricRegistries.getInstance();
+ }
+
+ @Override
+ public int getDataVersion() {
+ return SharedConstants.getGameVersion().getWorldVersion();
+ }
+
+ @Override
+ public DataFixer getDataFixer() {
+ return dataFixer;
+ }
+
+ @Override
+ public boolean isValidMobType(String type) {
+ return Registry.ENTITY_TYPE.containsId(new Identifier(type));
+ }
+
+ @Override
+ public void reload() {
+ getConfiguration().load();
+ }
+
+ @Override
+ public int schedule(long delay, long period, Runnable task) {
+ return -1;
+ }
+
+ @Override
+ public List extends World> getWorlds() {
+ Iterable worlds = server.getWorlds();
+ List ret = new ArrayList<>();
+ for (ServerWorld world : worlds) {
+ ret.add(new FabricWorld(world));
+ }
+ return ret;
+ }
+
+ @Nullable
+ @Override
+ public Player matchPlayer(Player player) {
+ if (player instanceof FabricPlayer) {
+ return player;
+ } else {
+ ServerPlayerEntity entity = server.getPlayerManager().getPlayer(player.getName());
+ return entity != null ? new FabricPlayer(entity) : null;
+ }
+ }
+
+ @Nullable
+ @Override
+ public World matchWorld(World world) {
+ if (world instanceof FabricWorld) {
+ return world;
+ } else {
+ for (ServerWorld ws : server.getWorlds()) {
+ if (ws.getLevelProperties().getLevelName().equals(world.getName())) {
+ return new FabricWorld(ws);
+ }
+ }
+
+ return null;
+ }
+ }
+
+ @Override
+ public void registerCommands(CommandManager manager) {
+ if (server == null) return;
+ net.minecraft.server.command.CommandManager mcMan = server.getCommandManager();
+
+ for (Command command : manager.getAllCommands().collect(toList())) {
+ CommandWrapper.register(mcMan.getDispatcher(), command);
+ Set perms = command.getCondition().as(PermissionCondition.class)
+ .map(PermissionCondition::getPermissions)
+ .orElseGet(Collections::emptySet);
+ if (!perms.isEmpty()) {
+ perms.forEach(FabricWorldEdit.inst.getPermissionsProvider()::registerPermission);
+ }
+ }
+ }
+
+ @Override
+ public void registerGameHooks() {
+ // We registered the events already anyway, so we just 'turn them on'
+ hookingEvents = true;
+ }
+
+ @Override
+ public FabricConfiguration getConfiguration() {
+ return mod.getConfig();
+ }
+
+ @Override
+ public String getVersion() {
+ return mod.getInternalVersion();
+ }
+
+ @Override
+ public String getPlatformName() {
+ return "Fabric-Official";
+ }
+
+ @Override
+ public String getPlatformVersion() {
+ return mod.getInternalVersion();
+ }
+
+ @Override
+ public Map getCapabilities() {
+ Map capabilities = new EnumMap<>(Capability.class);
+ capabilities.put(Capability.CONFIGURATION, Preference.PREFER_OTHERS);
+ capabilities.put(Capability.WORLDEDIT_CUI, Preference.NORMAL);
+ capabilities.put(Capability.GAME_HOOKS, Preference.NORMAL);
+ capabilities.put(Capability.PERMISSIONS, Preference.NORMAL);
+ capabilities.put(Capability.USER_COMMANDS, Preference.NORMAL);
+ capabilities.put(Capability.WORLD_EDITING, Preference.PREFERRED);
+ return capabilities;
+ }
+
+ @Override
+ public Collection getConnectedUsers() {
+ List users = new ArrayList<>();
+ PlayerManager scm = server.getPlayerManager();
+ for (ServerPlayerEntity entity : scm.getPlayerList()) {
+ if (entity != null) {
+ users.add(new FabricPlayer(entity));
+ }
+ }
+ return users;
+ }
+}
diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricPlayer.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricPlayer.java
new file mode 100644
index 000000000..1944b3063
--- /dev/null
+++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricPlayer.java
@@ -0,0 +1,267 @@
+/*
+ * 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 Lesser 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.fabric;
+
+import com.sk89q.jnbt.CompoundTag;
+import com.sk89q.util.StringUtil;
+import com.sk89q.worldedit.blocks.BaseItemStack;
+import com.sk89q.worldedit.entity.BaseEntity;
+import com.sk89q.worldedit.extension.platform.AbstractPlayerActor;
+import com.sk89q.worldedit.extent.inventory.BlockBag;
+import com.sk89q.worldedit.fabric.net.handler.WECUIPacketHandler;
+import com.sk89q.worldedit.internal.cui.CUIEvent;
+import com.sk89q.worldedit.math.BlockVector3;
+import com.sk89q.worldedit.math.Vector3;
+import com.sk89q.worldedit.session.SessionKey;
+import com.sk89q.worldedit.util.HandSide;
+import com.sk89q.worldedit.util.Location;
+import com.sk89q.worldedit.util.formatting.text.Component;
+import com.sk89q.worldedit.util.formatting.text.serializer.gson.GsonComponentSerializer;
+import com.sk89q.worldedit.world.World;
+import com.sk89q.worldedit.world.block.BaseBlock;
+import com.sk89q.worldedit.world.block.BlockStateHolder;
+import com.sk89q.worldedit.world.block.BlockTypes;
+import io.netty.buffer.Unpooled;
+import net.minecraft.ChatFormat;
+import net.minecraft.block.Block;
+import net.minecraft.client.network.packet.BlockEntityUpdateS2CPacket;
+import net.minecraft.client.network.packet.BlockUpdateS2CPacket;
+import net.minecraft.client.network.packet.CustomPayloadS2CPacket;
+import net.minecraft.item.ItemStack;
+import net.minecraft.network.chat.TextComponent;
+import net.minecraft.server.network.ServerPlayerEntity;
+import net.minecraft.util.Hand;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.PacketByteBuf;
+import net.minecraft.util.math.BlockPos;
+
+import java.io.IOException;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+public class FabricPlayer extends AbstractPlayerActor {
+
+ // see ClientPlayNetHandler: search for "invalid update packet", lots of hardcoded consts
+ private static final int STRUCTURE_BLOCK_PACKET_ID = 7;
+ private final ServerPlayerEntity player;
+
+ protected FabricPlayer(ServerPlayerEntity player) {
+ this.player = player;
+ ThreadSafeCache.getInstance().getOnlineIds().add(getUniqueId());
+ }
+
+ @Override
+ public UUID getUniqueId() {
+ return player.getUuid();
+ }
+
+ @Override
+ public BaseItemStack getItemInHand(HandSide handSide) {
+ ItemStack is = this.player.getStackInHand(handSide == HandSide.MAIN_HAND ? Hand.MAIN_HAND : Hand.OFF_HAND);
+ return FabricAdapter.adapt(is);
+ }
+
+ @Override
+ public String getName() {
+ return this.player.getName().getFormattedText();
+ }
+
+ @Override
+ public BaseEntity getState() {
+ throw new UnsupportedOperationException("Cannot create a state from this object");
+ }
+
+ @Override
+ public Location getLocation() {
+ Vector3 position = Vector3.at(this.player.x, this.player.y, this.player.z);
+ return new Location(
+ FabricWorldEdit.inst.getWorld(this.player.world),
+ position,
+ this.player.yaw,
+ this.player.pitch);
+ }
+
+ @Override
+ public boolean setLocation(Location location) {
+ // TODO
+ return false;
+ }
+
+ @Override
+ public com.sk89q.worldedit.world.World getWorld() {
+ return FabricWorldEdit.inst.getWorld(this.player.world);
+ }
+
+ @Override
+ public void giveItem(BaseItemStack itemStack) {
+ this.player.inventory.insertStack(FabricAdapter.adapt(itemStack));
+ }
+
+ @Override
+ public void dispatchCUIEvent(CUIEvent event) {
+ String[] params = event.getParameters();
+ String send = event.getTypeId();
+ if (params.length > 0) {
+ send = send + "|" + StringUtil.joinString(params, "|");
+ }
+ PacketByteBuf buffer = new PacketByteBuf(Unpooled.copiedBuffer(send.getBytes(WECUIPacketHandler.UTF_8_CHARSET)));
+ CustomPayloadS2CPacket packet = new CustomPayloadS2CPacket(new Identifier(FabricWorldEdit.MOD_ID, FabricWorldEdit.CUI_PLUGIN_CHANNEL), buffer);
+ this.player.networkHandler.sendPacket(packet);
+ }
+
+ @Override
+ public void printRaw(String msg) {
+ for (String part : msg.split("\n")) {
+ this.player.sendMessage(new TextComponent(part));
+ }
+ }
+
+ @Override
+ public void printDebug(String msg) {
+ sendColorized(msg, ChatFormat.GRAY);
+ }
+
+ @Override
+ public void print(String msg) {
+ sendColorized(msg, ChatFormat.LIGHT_PURPLE);
+ }
+
+ @Override
+ public void printError(String msg) {
+ sendColorized(msg, ChatFormat.RED);
+ }
+
+ @Override
+ public void print(Component component) {
+ this.player.sendMessage(net.minecraft.network.chat.Component.Serializer.fromJsonString(GsonComponentSerializer.INSTANCE.serialize(component)));
+ }
+
+ private void sendColorized(String msg, ChatFormat formatting) {
+ for (String part : msg.split("\n")) {
+ TextComponent component = new TextComponent(part);
+ component.getStyle().setColor(formatting);
+ this.player.sendMessage(component);
+ }
+ }
+
+ @Override
+ public void setPosition(Vector3 pos, float pitch, float yaw) {
+ this.player.networkHandler.requestTeleport(pos.getX(), pos.getY(), pos.getZ(), yaw, pitch);
+ }
+
+ @Override
+ public String[] getGroups() {
+ return new String[]{}; // WorldEditMod.inst.getPermissionsResolver().getGroups(this.player.username);
+ }
+
+ @Override
+ public BlockBag getInventoryBlockBag() {
+ return null;
+ }
+
+ @Override
+ public boolean hasPermission(String perm) {
+ return FabricWorldEdit.inst.getPermissionsProvider().hasPermission(player, perm);
+ }
+
+ @Nullable
+ @Override
+ public T getFacet(Class extends T> cls) {
+ return null;
+ }
+
+ @Override
+ public > void sendFakeBlock(BlockVector3 pos, B block) {
+ World world = getWorld();
+ if (!(world instanceof FabricWorld)) {
+ return;
+ }
+ BlockPos loc = FabricAdapter.toBlockPos(pos);
+ if (block == null) {
+ final BlockUpdateS2CPacket packetOut = new BlockUpdateS2CPacket(((FabricWorld) world).getWorld(), loc);
+ player.networkHandler.sendPacket(packetOut);
+ } else {
+ final BlockUpdateS2CPacket packetOut = new BlockUpdateS2CPacket();
+ PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer());
+ buf.writeBlockPos(loc);
+ buf.writeVarInt(Block.getRawIdFromState(FabricAdapter.adapt(block.toImmutableState())));
+ try {
+ packetOut.read(buf);
+ } catch (IOException e) {
+ return;
+ }
+ player.networkHandler.sendPacket(packetOut);
+ if (block instanceof BaseBlock && block.getBlockType().equals(BlockTypes.STRUCTURE_BLOCK)) {
+ final BaseBlock baseBlock = (BaseBlock) block;
+ final CompoundTag nbtData = baseBlock.getNbtData();
+ if (nbtData != null) {
+ player.networkHandler.sendPacket(new BlockEntityUpdateS2CPacket(
+ new BlockPos(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ()),
+ STRUCTURE_BLOCK_PACKET_ID,
+ NBTConverter.toNative(nbtData))
+ );
+ }
+ }
+ }
+ }
+
+ @Override
+ public SessionKey getSessionKey() {
+ return new SessionKeyImpl(player.getUuid(), player.getName().getString());
+ }
+
+ private static class SessionKeyImpl implements SessionKey {
+ // If not static, this will leak a reference
+
+ private final UUID uuid;
+ private final String name;
+
+ private SessionKeyImpl(UUID uuid, String name) {
+ this.uuid = uuid;
+ this.name = name;
+ }
+
+ @Override
+ public UUID getUniqueId() {
+ return uuid;
+ }
+
+ @Nullable
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public boolean isActive() {
+ // We can't directly check if the player is online because
+ // the list of players is not thread safe
+ return ThreadSafeCache.getInstance().getOnlineIds().contains(uuid);
+ }
+
+ @Override
+ public boolean isPersistent() {
+ return true;
+ }
+
+ }
+
+}
diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricRegistries.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricRegistries.java
new file mode 100644
index 000000000..7c703b723
--- /dev/null
+++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricRegistries.java
@@ -0,0 +1,75 @@
+/*
+ * 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 Lesser 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.fabric;
+
+import com.sk89q.worldedit.world.registry.BiomeRegistry;
+import com.sk89q.worldedit.world.registry.BlockCategoryRegistry;
+import com.sk89q.worldedit.world.registry.BlockRegistry;
+import com.sk89q.worldedit.world.registry.BundledRegistries;
+import com.sk89q.worldedit.world.registry.ItemCategoryRegistry;
+import com.sk89q.worldedit.world.registry.ItemRegistry;
+
+/**
+ * World data for the Fabric platform.
+ */
+class FabricRegistries extends BundledRegistries {
+
+ private static final FabricRegistries INSTANCE = new FabricRegistries();
+ private final BlockRegistry blockRegistry = new FabricBlockRegistry();
+ private final BiomeRegistry biomeRegistry = new FabricBiomeRegistry();
+ private final ItemRegistry itemRegistry = new FabricItemRegistry();
+ private final BlockCategoryRegistry blockCategoryRegistry = new FabricBlockCategoryRegistry();
+ private final ItemCategoryRegistry itemCategoryRegistry = new FabricItemCategoryRegistry();
+
+ @Override
+ public BlockRegistry getBlockRegistry() {
+ return blockRegistry;
+ }
+
+ @Override
+ public BiomeRegistry getBiomeRegistry() {
+ return biomeRegistry;
+ }
+
+ @Override
+ public ItemRegistry getItemRegistry() {
+ return itemRegistry;
+ }
+
+ @Override
+ public BlockCategoryRegistry getBlockCategoryRegistry() {
+ return blockCategoryRegistry;
+ }
+
+ @Override
+ public ItemCategoryRegistry getItemCategoryRegistry() {
+ return itemCategoryRegistry;
+ }
+
+ /**
+ * Get a static instance.
+ *
+ * @return an instance
+ */
+ public static FabricRegistries getInstance() {
+ return INSTANCE;
+ }
+
+}
diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricWorld.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricWorld.java
new file mode 100644
index 000000000..8125fb0cb
--- /dev/null
+++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricWorld.java
@@ -0,0 +1,597 @@
+/*
+ * 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 Lesser 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.fabric;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.io.Files;
+import com.sk89q.jnbt.CompoundTag;
+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.entity.BaseEntity;
+import com.sk89q.worldedit.entity.Entity;
+import com.sk89q.worldedit.internal.Constants;
+import com.sk89q.worldedit.internal.block.BlockStateIdAccess;
+import com.sk89q.worldedit.math.BlockVector2;
+import com.sk89q.worldedit.math.BlockVector3;
+import com.sk89q.worldedit.math.Vector3;
+import com.sk89q.worldedit.regions.CuboidRegion;
+import com.sk89q.worldedit.regions.Region;
+import com.sk89q.worldedit.util.Direction;
+import com.sk89q.worldedit.util.Location;
+import com.sk89q.worldedit.util.TreeGenerator.TreeType;
+import com.sk89q.worldedit.world.AbstractWorld;
+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.item.ItemTypes;
+import com.sk89q.worldedit.world.weather.WeatherType;
+import com.sk89q.worldedit.world.weather.WeatherTypes;
+import net.minecraft.block.Block;
+import net.minecraft.block.Blocks;
+import net.minecraft.block.LeavesBlock;
+import net.minecraft.block.entity.BlockEntity;
+import net.minecraft.entity.EntityType;
+import net.minecraft.entity.ItemEntity;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.ItemUsageContext;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.WorldGenerationProgressListener;
+import net.minecraft.server.world.ServerChunkManager;
+import net.minecraft.server.world.ServerWorld;
+import net.minecraft.util.ActionResult;
+import net.minecraft.util.Clearable;
+import net.minecraft.util.Hand;
+import net.minecraft.util.hit.BlockHitResult;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.ChunkPos;
+import net.minecraft.world.World;
+import net.minecraft.world.WorldSaveHandler;
+import net.minecraft.world.chunk.Chunk;
+import net.minecraft.world.chunk.ChunkManager;
+import net.minecraft.world.chunk.ChunkStatus;
+import net.minecraft.world.chunk.WorldChunk;
+import net.minecraft.world.gen.feature.BirchTreeFeature;
+import net.minecraft.world.gen.feature.DarkOakTreeFeature;
+import net.minecraft.world.gen.feature.DefaultFeatureConfig;
+import net.minecraft.world.gen.feature.Feature;
+import net.minecraft.world.gen.feature.FeatureConfig;
+import net.minecraft.world.gen.feature.HugeBrownMushroomFeature;
+import net.minecraft.world.gen.feature.HugeRedMushroomFeature;
+import net.minecraft.world.gen.feature.JungleGroundBushFeature;
+import net.minecraft.world.gen.feature.JungleTreeFeature;
+import net.minecraft.world.gen.feature.LargeOakTreeFeature;
+import net.minecraft.world.gen.feature.MegaJungleTreeFeature;
+import net.minecraft.world.gen.feature.MegaPineTreeFeature;
+import net.minecraft.world.gen.feature.OakTreeFeature;
+import net.minecraft.world.gen.feature.PineTreeFeature;
+import net.minecraft.world.gen.feature.PlantedFeatureConfig;
+import net.minecraft.world.gen.feature.SavannaTreeFeature;
+import net.minecraft.world.gen.feature.SpruceTreeFeature;
+import net.minecraft.world.gen.feature.SwampTreeFeature;
+import net.minecraft.world.level.LevelProperties;
+
+import java.io.File;
+import java.lang.ref.WeakReference;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.OptionalInt;
+import java.util.Random;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nullable;
+
+/**
+ * An adapter to Minecraft worlds for WorldEdit.
+ */
+public class FabricWorld extends AbstractWorld {
+
+ private static final Random random = new Random();
+ private static final int UPDATE = 1, NOTIFY = 2;
+
+ private static final net.minecraft.block.BlockState JUNGLE_LOG = Blocks.JUNGLE_LOG.getDefaultState();
+ private static final net.minecraft.block.BlockState JUNGLE_LEAF = Blocks.JUNGLE_LEAVES.getDefaultState().with(LeavesBlock.PERSISTENT, Boolean.TRUE);
+ private static final net.minecraft.block.BlockState JUNGLE_SHRUB = Blocks.OAK_LEAVES.getDefaultState().with(LeavesBlock.PERSISTENT, Boolean.TRUE);
+
+ private final WeakReference worldRef;
+
+ /**
+ * Construct a new world.
+ *
+ * @param world the world
+ */
+ FabricWorld(World world) {
+ checkNotNull(world);
+ this.worldRef = new WeakReference<>(world);
+ }
+
+ /**
+ * Get the underlying handle to the world.
+ *
+ * @return the world
+ * @throws WorldEditException thrown if a reference to the world was lost (i.e. world was unloaded)
+ */
+ public World getWorldChecked() throws WorldEditException {
+ World world = worldRef.get();
+ if (world != null) {
+ return world;
+ } else {
+ throw new WorldReferenceLostException("The reference to the world was lost (i.e. the world may have been unloaded)");
+ }
+ }
+
+ /**
+ * Get the underlying handle to the world.
+ *
+ * @return the world
+ * @throws RuntimeException thrown if a reference to the world was lost (i.e. world was unloaded)
+ */
+ public World getWorld() {
+ World world = worldRef.get();
+ if (world != null) {
+ return world;
+ } else {
+ throw new RuntimeException("The reference to the world was lost (i.e. the world may have been unloaded)");
+ }
+ }
+
+ @Override
+ public String getName() {
+ return getWorld().getLevelProperties().getLevelName();
+ }
+
+ @Override
+ public Path getStoragePath() {
+ final World world = getWorld();
+ if (world instanceof ServerWorld) {
+ return ((ServerWorld) world).getSaveHandler().getWorldDir().toPath();
+ }
+ return null;
+ }
+
+ @Override
+ public > boolean setBlock(BlockVector3 position, B block, boolean notifyAndLight) throws WorldEditException {
+ checkNotNull(position);
+ checkNotNull(block);
+
+ World world = getWorldChecked();
+ int x = position.getBlockX();
+ int y = position.getBlockY();
+ int z = position.getBlockZ();
+
+ // First set the block
+ Chunk chunk = world.getChunk(x >> 4, z >> 4);
+ BlockPos pos = new BlockPos(x, y, z);
+ net.minecraft.block.BlockState old = chunk.getBlockState(pos);
+ OptionalInt stateId = BlockStateIdAccess.getBlockStateId(block.toImmutableState());
+ net.minecraft.block.BlockState newState = stateId.isPresent() ? Block.getStateFromRawId(stateId.getAsInt()) : FabricAdapter.adapt(block.toImmutableState());
+ net.minecraft.block.BlockState successState = chunk.setBlockState(pos, newState, false);
+ boolean successful = successState != null;
+
+ // Create the TileEntity
+ if (successful || old == newState) {
+ if (block instanceof BaseBlock) {
+ CompoundTag tag = ((BaseBlock) block).getNbtData();
+ if (tag != null) {
+ net.minecraft.nbt.CompoundTag nativeTag = NBTConverter.toNative(tag);
+ nativeTag.putString("id", ((BaseBlock) block).getNbtId());
+ TileEntityUtils.setTileEntity(world, position, nativeTag);
+ successful = true; // update if TE changed as well
+ }
+ }
+ }
+
+ if (successful && notifyAndLight) {
+ world.getChunkManager().getLightingProvider().enqueueLightUpdate(pos);
+ world.updateListeners(pos, old, newState, UPDATE | NOTIFY);
+ }
+
+ return successful;
+ }
+
+ @Override
+ public boolean notifyAndLightBlock(BlockVector3 position, BlockState previousType) throws WorldEditException {
+ BlockPos pos = new BlockPos(position.getX(), position.getY(), position.getZ());
+ getWorld().updateListeners(pos, FabricAdapter.adapt(previousType), getWorld().getBlockState(pos), 1 | 2);
+ return true;
+ }
+
+ @Override
+ public int getBlockLightLevel(BlockVector3 position) {
+ checkNotNull(position);
+ return getWorld().getLightLevel(FabricAdapter.toBlockPos(position));
+ }
+
+ @Override
+ public boolean clearContainerBlockContents(BlockVector3 position) {
+ checkNotNull(position);
+ BlockEntity tile = getWorld().getBlockEntity(FabricAdapter.toBlockPos(position));
+ if ((tile instanceof Clearable)) {
+ ((Clearable) tile).clear();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public BiomeType getBiome(BlockVector2 position) {
+ checkNotNull(position);
+ return FabricAdapter.adapt(getWorld().getBiome(new BlockPos(position.getBlockX(), 0, position.getBlockZ())));
+ }
+
+ @Override
+ public boolean setBiome(BlockVector2 position, BiomeType biome) {
+ checkNotNull(position);
+ checkNotNull(biome);
+
+ Chunk chunk = getWorld().getChunk(position.getBlockX() >> 4, position.getBlockZ() >> 4, ChunkStatus.FULL, false);
+ if (chunk == null) {
+ return false;
+ }
+ chunk.getBiomeArray()[((position.getBlockZ() & 0xF) << 4 | position.getBlockX() & 0xF)] = FabricAdapter.adapt(biome);
+ return true;
+ }
+
+ private static final LoadingCache fakePlayers
+ = CacheBuilder.newBuilder().weakKeys().softValues().build(CacheLoader.from(WorldEditFakePlayer::new));
+
+ @Override
+ public boolean useItem(BlockVector3 position, BaseItem item, Direction face) {
+ ItemStack stack = FabricAdapter.adapt(new BaseItemStack(item.getType(), item.getNbtData(), 1));
+ ServerWorld world = (ServerWorld) getWorld();
+ final WorldEditFakePlayer fakePlayer;
+ try {
+ fakePlayer = fakePlayers.get(world);
+ } catch (ExecutionException ignored) {
+ return false;
+ }
+ fakePlayer.setStackInHand(Hand.MAIN_HAND, stack);
+ fakePlayer.setPositionAndAngles(position.getBlockX(), position.getBlockY(), position.getBlockZ(),
+ (float) face.toVector().toYaw(), (float) face.toVector().toPitch());
+ final BlockPos blockPos = FabricAdapter.toBlockPos(position);
+ final BlockHitResult rayTraceResult = new BlockHitResult(FabricAdapter.toVec3(position),
+ FabricAdapter.adapt(face), blockPos, false);
+ ItemUsageContext itemUseContext = new ItemUsageContext(fakePlayer, Hand.MAIN_HAND, rayTraceResult);
+ ActionResult used = stack.useOnBlock(itemUseContext);
+ if (used != ActionResult.SUCCESS) {
+ // try activating the block
+ if (getWorld().getBlockState(blockPos).activate(world, fakePlayer, Hand.MAIN_HAND, rayTraceResult)) {
+ used = ActionResult.SUCCESS;
+ } else {
+ used = stack.getItem().use(world, fakePlayer, Hand.MAIN_HAND).getResult();
+ }
+ }
+ return used == ActionResult.SUCCESS;
+ }
+
+ @Override
+ public void dropItem(Vector3 position, BaseItemStack item) {
+ checkNotNull(position);
+ checkNotNull(item);
+
+ if (item.getType() == ItemTypes.AIR) {
+ return;
+ }
+
+ ItemEntity entity = new ItemEntity(getWorld(), position.getX(), position.getY(), position.getZ(), FabricAdapter.adapt(item));
+ entity.setPickupDelay(10);
+ getWorld().spawnEntity(entity);
+ }
+
+ @Override
+ public void simulateBlockMine(BlockVector3 position) {
+ BlockPos pos = FabricAdapter.toBlockPos(position);
+ getWorld().breakBlock(pos, true);
+ }
+
+ @Override
+ public boolean regenerate(Region region, EditSession editSession) {
+ // Don't even try to regen if it's going to fail.
+ ChunkManager provider = getWorld().getChunkManager();
+ if (!(provider instanceof ServerChunkManager)) {
+ return false;
+ }
+
+ File saveFolder = Files.createTempDir();
+ // register this just in case something goes wrong
+ // normally it should be deleted at the end of this method
+ saveFolder.deleteOnExit();
+ try {
+ ServerWorld originalWorld = (ServerWorld) getWorld();
+
+ MinecraftServer server = originalWorld.getServer();
+ WorldSaveHandler saveHandler = new WorldSaveHandler(saveFolder, originalWorld.getSaveHandler().getWorldDir().getName(), server, server.getDataFixer());
+ World freshWorld = new ServerWorld(server, server.getWorkerExecutor(), saveHandler, originalWorld.getLevelProperties(),
+ originalWorld.dimension.getType(), originalWorld.getProfiler(), new NoOpChunkStatusListener());
+
+ // Pre-gen all the chunks
+ // We need to also pull one more chunk in every direction
+ CuboidRegion expandedPreGen = new CuboidRegion(region.getMinimumPoint().subtract(16, 0, 16), region.getMaximumPoint().add(16, 0, 16));
+ for (BlockVector2 chunk : expandedPreGen.getChunks()) {
+ freshWorld.getChunk(chunk.getBlockX(), chunk.getBlockZ());
+ }
+
+ FabricWorld from = new FabricWorld(freshWorld);
+ for (BlockVector3 vec : region) {
+ editSession.setBlock(vec, from.getFullBlock(vec));
+ }
+ } catch (MaxChangedBlocksException e) {
+ throw new RuntimeException(e);
+ } finally {
+ saveFolder.delete();
+ }
+
+ return true;
+ }
+
+ @Nullable
+ private static Feature extends FeatureConfig> createTreeFeatureGenerator(TreeType type) {
+ switch (type) {
+ case TREE: return new OakTreeFeature(DefaultFeatureConfig::deserialize, true);
+ case BIG_TREE: return new LargeOakTreeFeature(DefaultFeatureConfig::deserialize, true);
+ case REDWOOD: return new PineTreeFeature(DefaultFeatureConfig::deserialize);
+ case TALL_REDWOOD: return new SpruceTreeFeature(DefaultFeatureConfig::deserialize, true);
+ case BIRCH: return new BirchTreeFeature(DefaultFeatureConfig::deserialize, true, false);
+ case JUNGLE: return new MegaJungleTreeFeature(DefaultFeatureConfig::deserialize, true, 10, 20, JUNGLE_LOG, JUNGLE_LEAF);
+ case SMALL_JUNGLE: return new JungleTreeFeature(DefaultFeatureConfig::deserialize, true, 4 + random.nextInt(7), JUNGLE_LOG, JUNGLE_LEAF, false);
+ case SHORT_JUNGLE: return new JungleTreeFeature(DefaultFeatureConfig::deserialize, true, 4 + random.nextInt(7), JUNGLE_LOG, JUNGLE_LEAF, true);
+ case JUNGLE_BUSH: return new JungleGroundBushFeature(DefaultFeatureConfig::deserialize, JUNGLE_LOG, JUNGLE_SHRUB);
+ case SWAMP: return new SwampTreeFeature(DefaultFeatureConfig::deserialize);
+ case ACACIA: return new SavannaTreeFeature(DefaultFeatureConfig::deserialize, true);
+ case DARK_OAK: return new DarkOakTreeFeature(DefaultFeatureConfig::deserialize, true);
+ case MEGA_REDWOOD: return new MegaPineTreeFeature(DefaultFeatureConfig::deserialize, true, random.nextBoolean());
+ case TALL_BIRCH: return new BirchTreeFeature(DefaultFeatureConfig::deserialize, true, true);
+ case RED_MUSHROOM: return new HugeRedMushroomFeature(PlantedFeatureConfig::deserialize);
+ case BROWN_MUSHROOM: return new HugeBrownMushroomFeature(PlantedFeatureConfig::deserialize);
+ case RANDOM: return createTreeFeatureGenerator(TreeType.values()[ThreadLocalRandom.current().nextInt(TreeType.values().length)]);
+ default:
+ return null;
+ }
+ }
+
+ private FeatureConfig createFeatureConfig(TreeType type) {
+ if (type == TreeType.RED_MUSHROOM || type == TreeType.BROWN_MUSHROOM) {
+ return new PlantedFeatureConfig(true);
+ } else {
+ return new DefaultFeatureConfig();
+ }
+ }
+
+ @Override
+ public boolean generateTree(TreeType type, EditSession editSession, BlockVector3 position) throws MaxChangedBlocksException {
+ @SuppressWarnings("unchecked")
+ Feature generator = (Feature) createTreeFeatureGenerator(type);
+ return generator != null
+ && generator.generate(getWorld(), getWorld().getChunkManager().getChunkGenerator(), random,
+ FabricAdapter.toBlockPos(position), createFeatureConfig(type));
+ }
+
+ @Override
+ public void checkLoadedChunk(BlockVector3 pt) {
+ getWorld().getChunk(FabricAdapter.toBlockPos(pt));
+ }
+
+ @Override
+ public void fixAfterFastMode(Iterable chunks) {
+ fixLighting(chunks);
+ }
+
+ @Override
+ public void fixLighting(Iterable chunks) {
+ World world = getWorld();
+ for (BlockVector2 chunk : chunks) {
+ world.getChunkManager().getLightingProvider().suppressLight(new ChunkPos(chunk.getBlockX(), chunk.getBlockZ()), true);
+ }
+ }
+
+ @Override
+ public boolean playEffect(Vector3 position, int type, int data) {
+ getWorld().playLevelEvent(type, FabricAdapter.toBlockPos(position.toBlockPoint()), data);
+ return true;
+ }
+
+ @Override
+ public WeatherType getWeather() {
+ LevelProperties info = getWorld().getLevelProperties();
+ if (info.isThundering()) {
+ return WeatherTypes.THUNDER_STORM;
+ }
+ if (info.isRaining()) {
+ return WeatherTypes.RAIN;
+ }
+ return WeatherTypes.CLEAR;
+ }
+
+ @Override
+ public long getRemainingWeatherDuration() {
+ LevelProperties info = getWorld().getLevelProperties();
+ if (info.isThundering()) {
+ return info.getThunderTime();
+ }
+ if (info.isRaining()) {
+ return info.getRainTime();
+ }
+ return info.getClearWeatherTime();
+ }
+
+ @Override
+ public void setWeather(WeatherType weatherType) {
+ setWeather(weatherType, 0);
+ }
+
+ @Override
+ public void setWeather(WeatherType weatherType, long duration) {
+ LevelProperties info = getWorld().getLevelProperties();
+ if (weatherType == WeatherTypes.THUNDER_STORM) {
+ info.setClearWeatherTime(0);
+ info.setThundering(true);
+ info.setThunderTime((int) duration);
+ } else if (weatherType == WeatherTypes.RAIN) {
+ info.setClearWeatherTime(0);
+ info.setRaining(true);
+ info.setRainTime((int) duration);
+ } else if (weatherType == WeatherTypes.CLEAR) {
+ info.setRaining(false);
+ info.setThundering(false);
+ info.setClearWeatherTime((int) duration);
+ }
+ }
+
+ @Override
+ public int getMaxY() {
+ return getWorld().getHeight() - 1;
+ }
+
+ @Override
+ public BlockVector3 getSpawnPosition() {
+ return FabricAdapter.adapt(getWorld().getSpawnPos());
+ }
+
+ @Override
+ public BlockState getBlock(BlockVector3 position) {
+ net.minecraft.block.BlockState mcState = getWorld()
+ .getChunk(position.getBlockX() >> 4, position.getBlockZ() >> 4)
+ .getBlockState(FabricAdapter.toBlockPos(position));
+
+ BlockState matchingBlock = BlockStateIdAccess.getBlockStateById(Block.getRawIdFromState(mcState));
+ if (matchingBlock != null) {
+ return matchingBlock;
+ }
+
+ return FabricAdapter.adapt(mcState);
+ }
+
+ @Override
+ public BaseBlock getFullBlock(BlockVector3 position) {
+ BlockPos pos = new BlockPos(position.getBlockX(), position.getBlockY(), position.getBlockZ());
+ // Avoid creation by using the CHECK mode -- if it's needed, it'll be re-created anyways
+ BlockEntity tile = ((WorldChunk) getWorld().getChunk(pos)).getBlockEntity(pos, WorldChunk.CreationType.CHECK);
+
+ if (tile != null) {
+ return getBlock(position).toBaseBlock(NBTConverter.fromNative(TileEntityUtils.copyNbtData(tile)));
+ } else {
+ return getBlock(position).toBaseBlock();
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return getWorld().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if ((o instanceof FabricWorld)) {
+ FabricWorld other = ((FabricWorld) o);
+ World otherWorld = other.worldRef.get();
+ World thisWorld = worldRef.get();
+ return otherWorld != null && otherWorld.equals(thisWorld);
+ } else if (o instanceof com.sk89q.worldedit.world.World) {
+ return ((com.sk89q.worldedit.world.World) o).getName().equals(getName());
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public List extends Entity> getEntities(Region region) {
+ final World world = getWorld();
+ if (!(world instanceof ServerWorld)) {
+ return Collections.emptyList();
+ }
+ return ((ServerWorld) world).getEntities(null, entity -> true)
+ .stream()
+ .filter(e -> region.contains(FabricAdapter.adapt(e.getBlockPos())))
+ .map(FabricEntity::new).collect(Collectors.toList());
+ }
+
+ @Override
+ public List extends Entity> getEntities() {
+ final World world = getWorld();
+ if (!(world instanceof ServerWorld)) {
+ return Collections.emptyList();
+ }
+ return ((ServerWorld) world).getEntities(null, entity -> true)
+ .stream()
+ .map(FabricEntity::new)
+ .collect(Collectors.toList());
+ }
+
+ @Nullable
+ @Override
+ public Entity createEntity(Location location, BaseEntity entity) {
+ World world = getWorld();
+ final Optional> entityType = EntityType.get(entity.getType().getId());
+ if (!entityType.isPresent()) return null;
+ net.minecraft.entity.Entity createdEntity = entityType.get().create(world);
+ if (createdEntity != null) {
+ CompoundTag nativeTag = entity.getNbtData();
+ if (nativeTag != null) {
+ net.minecraft.nbt.CompoundTag tag = NBTConverter.toNative(entity.getNbtData());
+ for (String name : Constants.NO_COPY_ENTITY_NBT_FIELDS) {
+ tag.remove(name);
+ }
+ createdEntity.fromTag(tag);
+ }
+
+ createdEntity.setPositionAndAngles(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
+
+ world.spawnEntity(createdEntity);
+ return new FabricEntity(createdEntity);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Thrown when the reference to the world is lost.
+ */
+ @SuppressWarnings("serial")
+ private static final class WorldReferenceLostException extends WorldEditException {
+ private WorldReferenceLostException(String message) {
+ super(message);
+ }
+ }
+
+ private static class NoOpChunkStatusListener implements WorldGenerationProgressListener {
+ @Override
+ public void start(ChunkPos chunkPos) {
+ }
+
+ @Override
+ public void setChunkStatus(ChunkPos chunkPos, @Nullable ChunkStatus chunkStatus) {
+ }
+
+ @Override
+ public void stop() {
+ }
+ }
+}
diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricWorldEdit.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricWorldEdit.java
new file mode 100644
index 000000000..0175a618a
--- /dev/null
+++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricWorldEdit.java
@@ -0,0 +1,326 @@
+/*
+ * 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 Lesser 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.fabric;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.sk89q.worldedit.fabric.FabricAdapter.adaptPlayer;
+
+import com.sk89q.worldedit.LocalSession;
+import com.sk89q.worldedit.WorldEdit;
+import com.sk89q.worldedit.event.platform.PlatformReadyEvent;
+import com.sk89q.worldedit.extension.platform.Platform;
+import com.sk89q.worldedit.fabric.net.handler.WECUIPacketHandler;
+import com.sk89q.worldedit.util.Location;
+import com.sk89q.worldedit.world.biome.BiomeType;
+import com.sk89q.worldedit.world.block.BlockCategory;
+import com.sk89q.worldedit.world.block.BlockType;
+import com.sk89q.worldedit.world.entity.EntityType;
+import com.sk89q.worldedit.world.item.ItemCategory;
+import com.sk89q.worldedit.world.item.ItemType;
+import net.fabricmc.api.ModInitializer;
+import net.fabricmc.fabric.api.event.player.AttackBlockCallback;
+import net.fabricmc.fabric.api.event.player.UseBlockCallback;
+import net.fabricmc.fabric.api.event.player.UseItemCallback;
+import net.fabricmc.fabric.api.event.server.ServerStartCallback;
+import net.fabricmc.fabric.api.event.server.ServerStopCallback;
+import net.fabricmc.fabric.api.event.server.ServerTickCallback;
+import net.fabricmc.loader.api.FabricLoader;
+import net.fabricmc.loader.api.ModContainer;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.network.ServerPlayerEntity;
+import net.minecraft.tag.BlockTags;
+import net.minecraft.tag.ItemTags;
+import net.minecraft.util.ActionResult;
+import net.minecraft.util.Hand;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.hit.BlockHitResult;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.Direction;
+import net.minecraft.util.registry.Registry;
+import net.minecraft.world.World;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+/**
+ * The Fabric implementation of WorldEdit.
+ */
+public class FabricWorldEdit implements ModInitializer {
+
+ private static final Logger LOGGER = LogManager.getLogger();
+ public static final String MOD_ID = "worldedit";
+ public static final String CUI_PLUGIN_CHANNEL = "cui";
+
+ private FabricPermissionsProvider provider;
+
+ public static FabricWorldEdit inst;
+
+ private FabricPlatform platform;
+ private FabricConfiguration config;
+ private Path workingDir;
+
+ private ModContainer container;
+
+ public FabricWorldEdit() {
+ inst = this;
+ }
+
+ @Override
+ public void onInitialize() {
+ this.container = FabricLoader.getInstance().getModContainer("worldedit").orElseThrow(
+ () -> new IllegalStateException("WorldEdit mod missing in Fabric")
+ );
+
+ // Setup working directory
+ workingDir = new File(FabricLoader.getInstance().getConfigDirectory(), "worldedit").toPath();
+ if (!Files.exists(workingDir)) {
+ try {
+ Files.createDirectory(workingDir);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ WECUIPacketHandler.init();
+
+ ServerTickCallback.EVENT.register(ThreadSafeCache.getInstance());
+ ServerStartCallback.EVENT.register(this::onStartServer);
+ ServerStopCallback.EVENT.register(this::onStopServer);
+ AttackBlockCallback.EVENT.register(this::onLeftClickBlock);
+ UseBlockCallback.EVENT.register(this::onRightClickBlock);
+ UseItemCallback.EVENT.register(this::onRightClickAir);
+ LOGGER.info("WorldEdit for Fabric (version " + getInternalVersion() + ") is loaded");
+ }
+
+ private void setupPlatform(MinecraftServer server) {
+ this.platform = new FabricPlatform(this, server);
+
+ WorldEdit.getInstance().getPlatformManager().register(platform);
+
+ this.provider = new FabricPermissionsProvider.VanillaPermissionsProvider(platform);
+ }
+
+ private void setupRegistries() {
+ // Blocks
+ for (Identifier name : Registry.BLOCK.getIds()) {
+ if (BlockType.REGISTRY.get(name.toString()) == null) {
+ BlockType.REGISTRY.register(name.toString(), new BlockType(name.toString(),
+ input -> FabricAdapter.adapt(FabricAdapter.adapt(input.getBlockType()).getDefaultState())));
+ }
+ }
+ // Items
+ for (Identifier name : Registry.ITEM.getIds()) {
+ if (ItemType.REGISTRY.get(name.toString()) == null) {
+ ItemType.REGISTRY.register(name.toString(), new ItemType(name.toString()));
+ }
+ }
+ // Entities
+ for (Identifier name : Registry.ENTITY_TYPE.getIds()) {
+ if (EntityType.REGISTRY.get(name.toString()) == null) {
+ EntityType.REGISTRY.register(name.toString(), new EntityType(name.toString()));
+ }
+ }
+ // Biomes
+ for (Identifier name : Registry.BIOME.getIds()) {
+ if (BiomeType.REGISTRY.get(name.toString()) == null) {
+ BiomeType.REGISTRY.register(name.toString(), new BiomeType(name.toString()));
+ }
+ }
+ // Tags
+ for (Identifier name : BlockTags.getContainer().getKeys()) {
+ if (BlockCategory.REGISTRY.get(name.toString()) == null) {
+ BlockCategory.REGISTRY.register(name.toString(), new BlockCategory(name.toString()));
+ }
+ }
+ for (Identifier name : ItemTags.getContainer().getKeys()) {
+ if (ItemCategory.REGISTRY.get(name.toString()) == null) {
+ ItemCategory.REGISTRY.register(name.toString(), new ItemCategory(name.toString()));
+ }
+ }
+ }
+
+ private void onStartServer(MinecraftServer minecraftServer) {
+ setupPlatform(minecraftServer);
+ setupRegistries();
+
+ config = new FabricConfiguration(this);
+ config.load();
+ WorldEdit.getInstance().getEventBus().post(new PlatformReadyEvent());
+ }
+
+ private void onStopServer(MinecraftServer minecraftServer) {
+ WorldEdit worldEdit = WorldEdit.getInstance();
+ worldEdit.getSessionManager().unload();
+ worldEdit.getPlatformManager().unregister(platform);
+ }
+
+ private boolean shouldSkip() {
+ if (platform == null) {
+ return true;
+ }
+
+ return !platform.isHookingEvents(); // We have to be told to catch these events
+ }
+
+ private ActionResult onLeftClickBlock(PlayerEntity playerEntity, World world, Hand hand, BlockPos blockPos, Direction direction) {
+ if (shouldSkip() || hand == Hand.OFF_HAND || world.isClient) {
+ return ActionResult.PASS;
+ }
+
+ WorldEdit we = WorldEdit.getInstance();
+ FabricPlayer player = adaptPlayer((ServerPlayerEntity) playerEntity);
+ FabricWorld localWorld = getWorld(world);
+ Location pos = new Location(localWorld,
+ blockPos.getX(),
+ blockPos.getY(),
+ blockPos.getZ()
+ );
+
+ if (we.handleBlockLeftClick(player, pos)) {
+ return ActionResult.SUCCESS;
+ }
+
+ if (we.handleArmSwing(player)) {
+ return ActionResult.SUCCESS;
+ }
+
+ return ActionResult.PASS;
+ }
+
+ public void onLeftClickAir(PlayerEntity playerEntity, World world, Hand hand) {
+ WorldEdit we = WorldEdit.getInstance();
+ FabricPlayer player = adaptPlayer((ServerPlayerEntity) playerEntity);
+ we.handleArmSwing(player);
+ }
+
+ private ActionResult onRightClickBlock(PlayerEntity playerEntity, World world, Hand hand, BlockHitResult blockHitResult) {
+ if (shouldSkip() || hand == Hand.OFF_HAND || world.isClient) {
+ return ActionResult.PASS;
+ }
+
+ WorldEdit we = WorldEdit.getInstance();
+ FabricPlayer player = adaptPlayer((ServerPlayerEntity) playerEntity);
+ FabricWorld localWorld = getWorld(world);
+ Location pos = new Location(localWorld,
+ blockHitResult.getBlockPos().getX(),
+ blockHitResult.getBlockPos().getY(),
+ blockHitResult.getBlockPos().getZ()
+ );
+
+ if (we.handleBlockRightClick(player, pos)) {
+ return ActionResult.SUCCESS;
+ }
+
+ if (we.handleRightClick(player)) {
+ return ActionResult.SUCCESS;
+ }
+
+ return ActionResult.PASS;
+ }
+
+ private ActionResult onRightClickAir(PlayerEntity playerEntity, World world, Hand hand) {
+ if (shouldSkip() || hand == Hand.OFF_HAND || world.isClient) {
+ return ActionResult.PASS;
+ }
+
+ WorldEdit we = WorldEdit.getInstance();
+ FabricPlayer player = adaptPlayer((ServerPlayerEntity) playerEntity);
+
+ if (we.handleRightClick(player)) {
+ return ActionResult.SUCCESS;
+ }
+
+ return ActionResult.PASS;
+ }
+
+ // TODO Pass empty left click to server
+
+ /**
+ * Get the configuration.
+ *
+ * @return the Fabric configuration
+ */
+ FabricConfiguration getConfig() {
+ return this.config;
+ }
+
+ /**
+ * Get the session for a player.
+ *
+ * @param player the player
+ * @return the session
+ */
+ public LocalSession getSession(ServerPlayerEntity player) {
+ checkNotNull(player);
+ return WorldEdit.getInstance().getSessionManager().get(adaptPlayer(player));
+ }
+
+ /**
+ * Get the WorldEdit proxy for the given world.
+ *
+ * @param world the world
+ * @return the WorldEdit world
+ */
+ public FabricWorld getWorld(World world) {
+ checkNotNull(world);
+ return new FabricWorld(world);
+ }
+
+ /**
+ * Get the WorldEdit proxy for the platform.
+ *
+ * @return the WorldEdit platform
+ */
+ public Platform getPlatform() {
+ return this.platform;
+ }
+
+ /**
+ * Get the working directory where WorldEdit's files are stored.
+ *
+ * @return the working directory
+ */
+ public File getWorkingDir() {
+ return this.workingDir.toFile();
+ }
+
+ /**
+ * Get the version of the WorldEdit-Fabric implementation.
+ *
+ * @return a version string
+ */
+ String getInternalVersion() {
+ return container.getMetadata().getVersion().getFriendlyString();
+ }
+
+ public void setPermissionsProvider(FabricPermissionsProvider provider) {
+ this.provider = provider;
+ }
+
+ public FabricPermissionsProvider getPermissionsProvider() {
+ return provider;
+ }
+}
diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/NBTConverter.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/NBTConverter.java
new file mode 100644
index 000000000..450dfcb65
--- /dev/null
+++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/NBTConverter.java
@@ -0,0 +1,252 @@
+/*
+ * 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 Lesser 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.fabric;
+
+import com.sk89q.jnbt.ByteArrayTag;
+import com.sk89q.jnbt.ByteTag;
+import com.sk89q.jnbt.CompoundTag;
+import com.sk89q.jnbt.DoubleTag;
+import com.sk89q.jnbt.EndTag;
+import com.sk89q.jnbt.FloatTag;
+import com.sk89q.jnbt.IntArrayTag;
+import com.sk89q.jnbt.IntTag;
+import com.sk89q.jnbt.ListTag;
+import com.sk89q.jnbt.LongTag;
+import com.sk89q.jnbt.ShortTag;
+import com.sk89q.jnbt.StringTag;
+import com.sk89q.jnbt.Tag;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * Converts between JNBT and Minecraft NBT classes.
+ */
+final class NBTConverter {
+
+ private NBTConverter() {
+ }
+
+ public static net.minecraft.nbt.Tag toNative(Tag tag) {
+ if (tag instanceof IntArrayTag) {
+ return toNative((IntArrayTag) tag);
+
+ } else if (tag instanceof ListTag) {
+ return toNative((ListTag) tag);
+
+ } else if (tag instanceof LongTag) {
+ return toNative((LongTag) tag);
+
+ } else if (tag instanceof StringTag) {
+ return toNative((StringTag) tag);
+
+ } else if (tag instanceof IntTag) {
+ return toNative((IntTag) tag);
+
+ } else if (tag instanceof ByteTag) {
+ return toNative((ByteTag) tag);
+
+ } else if (tag instanceof ByteArrayTag) {
+ return toNative((ByteArrayTag) tag);
+
+ } else if (tag instanceof CompoundTag) {
+ return toNative((CompoundTag) tag);
+
+ } else if (tag instanceof FloatTag) {
+ return toNative((FloatTag) tag);
+
+ } else if (tag instanceof ShortTag) {
+ return toNative((ShortTag) tag);
+
+ } else if (tag instanceof DoubleTag) {
+ return toNative((DoubleTag) tag);
+ } else {
+ throw new IllegalArgumentException("Can't convert tag of type " + tag.getClass().getCanonicalName());
+ }
+ }
+
+ public static net.minecraft.nbt.IntArrayTag toNative(IntArrayTag tag) {
+ int[] value = tag.getValue();
+ return new net.minecraft.nbt.IntArrayTag(Arrays.copyOf(value, value.length));
+ }
+
+ public static net.minecraft.nbt.ListTag toNative(ListTag tag) {
+ net.minecraft.nbt.ListTag list = new net.minecraft.nbt.ListTag();
+ for (Tag child : tag.getValue()) {
+ if (child instanceof EndTag) {
+ continue;
+ }
+ list.add(toNative(child));
+ }
+ return list;
+ }
+
+ public static net.minecraft.nbt.LongTag toNative(LongTag tag) {
+ return new net.minecraft.nbt.LongTag(tag.getValue());
+ }
+
+ public static net.minecraft.nbt.StringTag toNative(StringTag tag) {
+ return new net.minecraft.nbt.StringTag(tag.getValue());
+ }
+
+ public static net.minecraft.nbt.IntTag toNative(IntTag tag) {
+ return new net.minecraft.nbt.IntTag(tag.getValue());
+ }
+
+ public static net.minecraft.nbt.ByteTag toNative(ByteTag tag) {
+ return new net.minecraft.nbt.ByteTag(tag.getValue());
+ }
+
+ public static net.minecraft.nbt.ByteArrayTag toNative(ByteArrayTag tag) {
+ byte[] value = tag.getValue();
+ return new net.minecraft.nbt.ByteArrayTag(Arrays.copyOf(value, value.length));
+ }
+
+ public static net.minecraft.nbt.CompoundTag toNative(CompoundTag tag) {
+ net.minecraft.nbt.CompoundTag compound = new net.minecraft.nbt.CompoundTag();
+ for (Entry child : tag.getValue().entrySet()) {
+ compound.put(child.getKey(), toNative(child.getValue()));
+ }
+ return compound;
+ }
+
+ public static net.minecraft.nbt.FloatTag toNative(FloatTag tag) {
+ return new net.minecraft.nbt.FloatTag(tag.getValue());
+ }
+
+ public static net.minecraft.nbt.ShortTag toNative(ShortTag tag) {
+ return new net.minecraft.nbt.ShortTag(tag.getValue());
+ }
+
+ public static net.minecraft.nbt.DoubleTag toNative(DoubleTag tag) {
+ return new net.minecraft.nbt.DoubleTag(tag.getValue());
+ }
+
+ public static Tag fromNative(net.minecraft.nbt.Tag other) {
+ if (other instanceof net.minecraft.nbt.IntArrayTag) {
+ return fromNative((net.minecraft.nbt.IntArrayTag) other);
+
+ } else if (other instanceof net.minecraft.nbt.ListTag) {
+ return fromNative((net.minecraft.nbt.ListTag) other);
+
+ } else if (other instanceof net.minecraft.nbt.EndTag) {
+ return fromNative((net.minecraft.nbt.EndTag) other);
+
+ } else if (other instanceof net.minecraft.nbt.LongTag) {
+ return fromNative((net.minecraft.nbt.LongTag) other);
+
+ } else if (other instanceof net.minecraft.nbt.StringTag) {
+ return fromNative((net.minecraft.nbt.StringTag) other);
+
+ } else if (other instanceof net.minecraft.nbt.IntTag) {
+ return fromNative((net.minecraft.nbt.IntTag) other);
+
+ } else if (other instanceof net.minecraft.nbt.ByteTag) {
+ return fromNative((net.minecraft.nbt.ByteTag) other);
+
+ } else if (other instanceof net.minecraft.nbt.ByteArrayTag) {
+ return fromNative((net.minecraft.nbt.ByteArrayTag) other);
+
+ } else if (other instanceof net.minecraft.nbt.CompoundTag) {
+ return fromNative((net.minecraft.nbt.CompoundTag) other);
+
+ } else if (other instanceof net.minecraft.nbt.FloatTag) {
+ return fromNative((net.minecraft.nbt.FloatTag) other);
+
+ } else if (other instanceof net.minecraft.nbt.ShortTag) {
+ return fromNative((net.minecraft.nbt.ShortTag) other);
+
+ } else if (other instanceof net.minecraft.nbt.DoubleTag) {
+ return fromNative((net.minecraft.nbt.DoubleTag) other);
+ } else {
+ throw new IllegalArgumentException("Can't convert other of type " + other.getClass().getCanonicalName());
+ }
+ }
+
+ public static IntArrayTag fromNative(net.minecraft.nbt.IntArrayTag other) {
+ int[] value = other.getIntArray();
+ return new IntArrayTag(Arrays.copyOf(value, value.length));
+ }
+
+ public static ListTag fromNative(net.minecraft.nbt.ListTag other) {
+ other = (net.minecraft.nbt.ListTag) other.copy();
+ List list = new ArrayList<>();
+ Class extends Tag> listClass = StringTag.class;
+ int tags = other.size();
+ for (int i = 0; i < tags; i++) {
+ Tag child = fromNative(other.remove(0));
+ list.add(child);
+ listClass = child.getClass();
+ }
+ return new ListTag(listClass, list);
+ }
+
+ public static EndTag fromNative(net.minecraft.nbt.EndTag other) {
+ return new EndTag();
+ }
+
+ public static LongTag fromNative(net.minecraft.nbt.LongTag other) {
+ return new LongTag(other.getLong());
+ }
+
+ public static StringTag fromNative(net.minecraft.nbt.StringTag other) {
+ return new StringTag(other.asString());
+ }
+
+ public static IntTag fromNative(net.minecraft.nbt.IntTag other) {
+ return new IntTag(other.getInt());
+ }
+
+ public static ByteTag fromNative(net.minecraft.nbt.ByteTag other) {
+ return new ByteTag(other.getByte());
+ }
+
+ public static ByteArrayTag fromNative(net.minecraft.nbt.ByteArrayTag other) {
+ byte[] value = other.getByteArray();
+ return new ByteArrayTag(Arrays.copyOf(value, value.length));
+ }
+
+ public static CompoundTag fromNative(net.minecraft.nbt.CompoundTag other) {
+ Set tags = other.getKeys();
+ Map map = new HashMap<>();
+ for (String tagName : tags) {
+ map.put(tagName, fromNative(other.getTag(tagName)));
+ }
+ return new CompoundTag(map);
+ }
+
+ public static FloatTag fromNative(net.minecraft.nbt.FloatTag other) {
+ return new FloatTag(other.getFloat());
+ }
+
+ public static ShortTag fromNative(net.minecraft.nbt.ShortTag other) {
+ return new ShortTag(other.getShort());
+ }
+
+ public static DoubleTag fromNative(net.minecraft.nbt.DoubleTag other) {
+ return new DoubleTag(other.getDouble());
+ }
+
+}
diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/PropertyAdapter.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/PropertyAdapter.java
new file mode 100644
index 000000000..fb46415cd
--- /dev/null
+++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/PropertyAdapter.java
@@ -0,0 +1,70 @@
+/*
+ * 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 Lesser 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.fabric;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.collect.ImmutableList;
+import com.sk89q.worldedit.registry.state.Property;
+
+import java.util.List;
+import java.util.Optional;
+
+class PropertyAdapter> implements Property {
+
+ private final net.minecraft.state.property.Property property;
+ private final List values;
+
+ public PropertyAdapter(net.minecraft.state.property.Property property) {
+ this.property = property;
+ this.values = ImmutableList.copyOf(property.getValues());
+ }
+
+ @Override
+ public String getName() {
+ return property.getName();
+ }
+
+ @Override
+ public List getValues() {
+ return values;
+ }
+
+ @Override
+ public T getValueFor(String string) throws IllegalArgumentException {
+ Optional val = property.getValue(string);
+ checkArgument(val.isPresent(), "%s has no value for %s", getName(), string);
+ return val.get();
+ }
+
+ @Override
+ public int hashCode() {
+ return getName().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Property)) {
+ return false;
+ }
+ return getName().equals(((Property>) obj).getName());
+ }
+
+}
diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/ThreadSafeCache.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/ThreadSafeCache.java
new file mode 100644
index 000000000..491d80638
--- /dev/null
+++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/ThreadSafeCache.java
@@ -0,0 +1,76 @@
+/*
+ * 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 Lesser 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.fabric;
+
+import net.fabricmc.fabric.api.event.server.ServerTickCallback;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.network.ServerPlayerEntity;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+/**
+ * Caches data that cannot be accessed from another thread safely.
+ */
+public class ThreadSafeCache implements ServerTickCallback {
+
+ private static final long REFRESH_DELAY = 1000 * 30;
+ private static final ThreadSafeCache INSTANCE = new ThreadSafeCache();
+ private Set onlineIds = Collections.emptySet();
+ private long lastRefresh = 0;
+
+ /**
+ * Get an concurrent-safe set of UUIDs of online players.
+ *
+ * @return a set of UUIDs
+ */
+ public Set getOnlineIds() {
+ return onlineIds;
+ }
+
+ @Override
+ public void tick(MinecraftServer server) {
+ long now = System.currentTimeMillis();
+
+ if (now - lastRefresh > REFRESH_DELAY) {
+ Set onlineIds = new HashSet<>();
+
+ if (server == null) {
+ return;
+ }
+ for (ServerPlayerEntity player : server.getPlayerManager().getPlayerList()) {
+ if (player != null) {
+ onlineIds.add(player.getUuid());
+ }
+ }
+
+ this.onlineIds = new CopyOnWriteArraySet<>(onlineIds);
+
+ lastRefresh = now;
+ }
+ }
+
+ public static ThreadSafeCache getInstance() {
+ return INSTANCE;
+ }
+}
diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/TileEntityUtils.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/TileEntityUtils.java
new file mode 100644
index 000000000..4faec9d9e
--- /dev/null
+++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/TileEntityUtils.java
@@ -0,0 +1,79 @@
+/*
+ * 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 Lesser 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.fabric;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.sk89q.worldedit.math.BlockVector3;
+import net.minecraft.block.entity.BlockEntity;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.nbt.IntTag;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.world.World;
+
+import javax.annotation.Nullable;
+
+/**
+ * Utility methods for setting tile entities in the world.
+ */
+final class TileEntityUtils {
+
+ private TileEntityUtils() {
+ }
+
+ /**
+ * Update the given tag compound with position information.
+ *
+ * @param tag the tag
+ * @param position the position
+ */
+ private static void updateForSet(CompoundTag tag, BlockVector3 position) {
+ checkNotNull(tag);
+ checkNotNull(position);
+
+ tag.put("x", new IntTag(position.getBlockX()));
+ tag.put("y", new IntTag(position.getBlockY()));
+ tag.put("z", new IntTag(position.getBlockZ()));
+ }
+
+ /**
+ * Set a tile entity at the given location using the tile entity ID from
+ * the tag.
+ *
+ * @param world the world
+ * @param position the position
+ * @param tag the tag for the tile entity (may be null to do nothing)
+ */
+ static void setTileEntity(World world, BlockVector3 position, @Nullable CompoundTag tag) {
+ if (tag != null) {
+ updateForSet(tag, position);
+ BlockEntity tileEntity = BlockEntity.createFromTag(tag);
+ if (tileEntity != null) {
+ world.setBlockEntity(new BlockPos(position.getBlockX(), position.getBlockY(), position.getBlockZ()), tileEntity);
+ }
+ }
+ }
+
+ public static CompoundTag copyNbtData(BlockEntity tile) {
+ CompoundTag tag = new CompoundTag();
+ tile.toTag(tag);
+ return tag;
+ }
+}
diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/WorldEditFakePlayer.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/WorldEditFakePlayer.java
new file mode 100644
index 000000000..ea65774b7
--- /dev/null
+++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/WorldEditFakePlayer.java
@@ -0,0 +1,73 @@
+/*
+ * 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 Lesser 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.fabric;
+
+import com.mojang.authlib.GameProfile;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.damage.DamageSource;
+import net.minecraft.network.chat.Component;
+import net.minecraft.server.network.ServerPlayerEntity;
+import net.minecraft.server.network.ServerPlayerInteractionManager;
+import net.minecraft.server.world.ServerWorld;
+import net.minecraft.stat.Stat;
+import net.minecraft.world.dimension.DimensionType;
+
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+public class WorldEditFakePlayer extends ServerPlayerEntity {
+ private static final GameProfile FAKE_WORLDEDIT_PROFILE = new GameProfile(UUID.nameUUIDFromBytes("worldedit".getBytes()), "[WorldEdit]");
+
+ public WorldEditFakePlayer(ServerWorld world) {
+ super(world.getServer(), world, FAKE_WORLDEDIT_PROFILE, new ServerPlayerInteractionManager(world));
+ }
+
+ @Override
+ public void tick() {
+ }
+
+ @Override
+ public void increaseStat(Stat> stat, int incrementer) {
+ }
+
+ @Override
+ public void incrementStat(Stat> stat) {
+ }
+
+ @Override
+ public void sendMessage(Component component) {
+ }
+
+ @Override
+ public void addChatMessage(Component component, boolean opt) {
+ }
+
+ @Nullable
+ @Override
+ public Entity changeDimension(DimensionType dimensionType) {
+ return this;
+ }
+
+ @Override
+ public boolean isInvulnerableTo(DamageSource damageSource) {
+ return true;
+ }
+}
diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/mixin/MixinServerPlayerEntity.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/mixin/MixinServerPlayerEntity.java
new file mode 100644
index 000000000..fbad989c1
--- /dev/null
+++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/mixin/MixinServerPlayerEntity.java
@@ -0,0 +1,45 @@
+/*
+ * 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 Lesser 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.fabric.mixin;
+
+import com.mojang.authlib.GameProfile;
+import com.sk89q.worldedit.fabric.FabricWorldEdit;
+import net.minecraft.container.ContainerListener;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.server.network.ServerPlayerEntity;
+import net.minecraft.util.Hand;
+import net.minecraft.world.World;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+@Mixin(ServerPlayerEntity.class)
+public abstract class MixinServerPlayerEntity extends PlayerEntity implements ContainerListener {
+
+ public MixinServerPlayerEntity(World world, GameProfile gameProfile) {
+ super(world, gameProfile);
+ }
+
+ @Inject(method = "swingHand", at = @At(value = "HEAD"))
+ public void onSwing(Hand hand, CallbackInfo injectionInfo) {
+ FabricWorldEdit.inst.onLeftClickAir(this, this.world, hand);
+ }
+}
diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/net/handler/WECUIPacketHandler.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/net/handler/WECUIPacketHandler.java
new file mode 100644
index 000000000..816d6c76f
--- /dev/null
+++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/net/handler/WECUIPacketHandler.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 Lesser 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.fabric.net.handler;
+
+import com.sk89q.worldedit.LocalSession;
+import com.sk89q.worldedit.fabric.FabricAdapter;
+import com.sk89q.worldedit.fabric.FabricPlayer;
+import com.sk89q.worldedit.fabric.FabricWorldEdit;
+import net.fabricmc.fabric.api.network.PacketConsumer;
+import net.fabricmc.fabric.api.network.PacketContext;
+import net.fabricmc.fabric.api.network.ServerSidePacketRegistry;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.network.packet.CustomPayloadS2CPacket;
+import net.minecraft.server.network.ServerPlayerEntity;
+import net.minecraft.server.network.packet.CustomPayloadC2SPacket;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.PacketByteBuf;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+public final class WECUIPacketHandler {
+ private WECUIPacketHandler() {
+ }
+
+ public static final Charset UTF_8_CHARSET = StandardCharsets.UTF_8;
+ private static final Identifier CUI_IDENTIFIER = new Identifier(FabricWorldEdit.MOD_ID, FabricWorldEdit.CUI_PLUGIN_CHANNEL);
+
+ public static void init() {
+ ServerSidePacketRegistry.INSTANCE.register(CUI_IDENTIFIER, (packetContext, packetByteBuf) -> {
+ ServerPlayerEntity player = (ServerPlayerEntity) packetContext.getPlayer();
+ LocalSession session = FabricWorldEdit.inst.getSession(player);
+
+ if (session.hasCUISupport()) {
+ return;
+ }
+
+ String text = packetByteBuf.toString(UTF_8_CHARSET);
+ final FabricPlayer actor = FabricAdapter.adaptPlayer(player);
+ session.handleCUIInitializationMessage(text, actor);
+ session.describeCUI(actor);
+ });
+ }
+}
\ No newline at end of file
diff --git a/worldedit-fabric/src/main/resources/assets/worldedit/icon.png b/worldedit-fabric/src/main/resources/assets/worldedit/icon.png
new file mode 100644
index 000000000..dc269dcf7
Binary files /dev/null and b/worldedit-fabric/src/main/resources/assets/worldedit/icon.png differ
diff --git a/worldedit-fabric/src/main/resources/defaults/worldedit.properties b/worldedit-fabric/src/main/resources/defaults/worldedit.properties
new file mode 100644
index 000000000..d0296c15c
--- /dev/null
+++ b/worldedit-fabric/src/main/resources/defaults/worldedit.properties
@@ -0,0 +1,33 @@
+#Don't put comments; they get removed
+default-max-polygon-points=-1
+schematic-save-dir=schematics
+super-pickaxe-many-drop-items=true
+register-help=true
+nav-wand-item=minecraft:compass
+profile=false
+trace-unflushed-sessions=false
+super-pickaxe-drop-items=true
+disallowed-blocks=minecraft:oak_sapling,minecraft:jungle_sapling,minecraft:dark_oak_sapling,minecraft:spruce_sapling,minecraft:birch_sapling,minecraft:acacia_sapling,minecraft:black_bed,minecraft:blue_bed,minecraft:brown_bed,minecraft:cyan_bed,minecraft:gray_bed,minecraft:green_bed,minecraft:light_blue_bed,minecraft:light_gray_bed,minecraft:lime_bed,minecraft:magenta_bed,minecraft:orange_bed,minecraft:pink_bed,minecraft:purple_bed,minecraft:red_bed,minecraft:white_bed,minecraft:yellow_bed,minecraft:powered_rail,minecraft:detector_rail,minecraft:grass,minecraft:dead_bush,minecraft:moving_piston,minecraft:piston_head,minecraft:sunflower,minecraft:rose_bush,minecraft:dandelion,minecraft:poppy,minecraft:brown_mushroom,minecraft:red_mushroom,minecraft:tnt,minecraft:torch,minecraft:fire,minecraft:redstone_wire,minecraft:wheat,minecraft:potatoes,minecraft:carrots,minecraft:melon_stem,minecraft:pumpkin_stem,minecraft:beetroots,minecraft:rail,minecraft:lever,minecraft:redstone_torch,minecraft:redstone_wall_torch,minecraft:repeater,minecraft:comparator,minecraft:stone_button,minecraft:birch_button,minecraft:acacia_button,minecraft:dark_oak_button,minecraft:jungle_button,minecraft:oak_button,minecraft:spruce_button,minecraft:cactus,minecraft:sugar_cane,minecraft:bedrock
+max-super-pickaxe-size=5
+max-brush-radius=10
+craftscript-dir=craftscripts
+no-double-slash=false
+wand-item=minecraft:wooden_axe
+shell-save-type=
+scripting-timeout=3000
+snapshots-dir=
+use-inventory-creative-override=false
+log-file=worldedit.log
+log-format=[%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS %4$s]: %5$s%6$s%n
+max-changed-blocks=-1
+nav-wand-distance=50
+butcher-default-radius=-1
+default-max-changed-blocks=-1
+history-size=15
+use-inventory=false
+allow-symbolic-links=false
+use-inventory-override=false
+log-commands=false
+butcher-max-radius=-1
+max-polygon-points=20
+max-radius=-1
diff --git a/worldedit-fabric/src/main/resources/fabric.mod.json b/worldedit-fabric/src/main/resources/fabric.mod.json
new file mode 100644
index 000000000..ee3e7b612
--- /dev/null
+++ b/worldedit-fabric/src/main/resources/fabric.mod.json
@@ -0,0 +1,37 @@
+{
+ "schemaVersion": 1,
+ "id": "worldedit",
+ "version": "${version}",
+
+ "name": "WorldEdit",
+ "description": "WorldEdit is an easy-to-use in-game world editor for Minecraft, supporting both single- and multi-player.",
+ "authors": [
+ "sk89q",
+ "wizjany",
+ "TomyLobo",
+ "kenzierocks",
+ "Me4502"
+ ],
+ "contact": {
+ "homepage": "https://enginehub.org/worldedit/",
+ "sources": "https://github.com/EngineHub/WorldEdit"
+ },
+
+ "license": "GPL3",
+ "icon": "assets/worldedit/icon.png",
+
+ "environment": "*",
+ "entrypoints": {
+ "main": [
+ "com.sk89q.worldedit.fabric.FabricWorldEdit"
+ ]
+ },
+
+ "depends": {
+ "fabricloader": ">=0.4.0",
+ "fabric": "*"
+ },
+ "mixins": [
+ "worldedit.mixins.json"
+ ]
+}
\ No newline at end of file
diff --git a/worldedit-fabric/src/main/resources/pack.mcmeta b/worldedit-fabric/src/main/resources/pack.mcmeta
new file mode 100644
index 000000000..b48da3b7d
--- /dev/null
+++ b/worldedit-fabric/src/main/resources/pack.mcmeta
@@ -0,0 +1,6 @@
+{
+ "pack": {
+ "description": "WorldEdit Resources",
+ "pack_format": 4
+ }
+}
\ No newline at end of file
diff --git a/worldedit-fabric/src/main/resources/worldedit.mixins.json b/worldedit-fabric/src/main/resources/worldedit.mixins.json
new file mode 100644
index 000000000..165bf44ce
--- /dev/null
+++ b/worldedit-fabric/src/main/resources/worldedit.mixins.json
@@ -0,0 +1,13 @@
+{
+ "required": true,
+ "package": "com.sk89q.worldedit.fabric.mixin",
+ "compatibilityLevel": "JAVA_8",
+ "mixins": [
+ "MixinServerPlayerEntity"
+ ],
+ "server": [
+ ],
+ "injectors": {
+ "defaultRequire": 1
+ }
+}
\ No newline at end of file
diff --git a/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgePlayer.java b/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgePlayer.java
index 37f1a67ff..a48ff6256 100644
--- a/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgePlayer.java
+++ b/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgePlayer.java
@@ -124,7 +124,7 @@ public class ForgePlayer extends AbstractPlayerActor {
send = send + "|" + StringUtil.joinString(params, "|");
}
PacketBuffer buffer = new PacketBuffer(Unpooled.copiedBuffer(send.getBytes(WECUIPacketHandler.UTF_8_CHARSET)));
- SCustomPayloadPlayPacket packet = new SCustomPayloadPlayPacket(new ResourceLocation(ForgeWorldEdit.CUI_PLUGIN_CHANNEL), buffer);
+ SCustomPayloadPlayPacket packet = new SCustomPayloadPlayPacket(new ResourceLocation(ForgeWorldEdit.MOD_ID, ForgeWorldEdit.CUI_PLUGIN_CHANNEL), buffer);
this.player.connection.sendPacket(packet);
}