diff --git a/core/src/main/java/com/boydti/fawe/Fawe.java b/core/src/main/java/com/boydti/fawe/Fawe.java index 72f76516..118a0638 100644 --- a/core/src/main/java/com/boydti/fawe/Fawe.java +++ b/core/src/main/java/com/boydti/fawe/Fawe.java @@ -47,7 +47,9 @@ import com.sk89q.worldedit.history.change.EntityCreate; import com.sk89q.worldedit.history.change.EntityRemove; import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.selector.CuboidRegionSelector; +import com.sk89q.worldedit.world.registry.BundledBlockData; import java.io.File; +import java.io.IOException; import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; import java.lang.management.MemoryPoolMXBean; @@ -155,6 +157,7 @@ public class Fawe { private Thread thread = Thread.currentThread(); private Fawe(final IFawe implementation) { + this.INSTANCE = this; this.IMP = implementation; this.thread = Thread.currentThread(); /* @@ -212,6 +215,21 @@ public class Fawe { Settings.setup(new File(this.IMP.getDirectory(), "config.yml")); // Setting up message.yml BBC.load(new File(this.IMP.getDirectory(), "message.yml")); + // Block rotation + try { + BundledBlockData.getInstance().loadFromResource(); + } catch (IOException e) { + e.printStackTrace(); + } + File jar = MainUtil.getJarFile(); + File file = MainUtil.copyFile(jar, "extrablocks.json", null); + if (file != null && file.exists()) { + try { + BundledBlockData.getInstance().add(file.toURI().toURL(), false); + } catch (IOException e) { + e.printStackTrace(); + } + } } private WorldEdit worldedit; @@ -266,6 +284,7 @@ public class Fawe { Operations.inject(); // Optimizations // BlockData BlockData.inject(); // Temporary fix for 1.9.4 + BundledBlockData.inject(); // Add custom rotation try { CommandManager.inject(); // Async commands PlatformManager.inject(); // Async brushes / tools diff --git a/core/src/main/java/com/boydti/fawe/FaweAPI.java b/core/src/main/java/com/boydti/fawe/FaweAPI.java index 27d7ab09..6b517d66 100644 --- a/core/src/main/java/com/boydti/fawe/FaweAPI.java +++ b/core/src/main/java/com/boydti/fawe/FaweAPI.java @@ -4,6 +4,7 @@ import com.boydti.fawe.config.BBC; import com.boydti.fawe.config.Settings; import com.boydti.fawe.object.FaweLocation; import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.object.PseudoRandom; import com.boydti.fawe.object.RegionWrapper; import com.boydti.fawe.object.changeset.DiskStorageHistory; import com.boydti.fawe.regions.FaweMaskManager; @@ -12,7 +13,6 @@ import com.boydti.fawe.util.MemUtil; import com.boydti.fawe.util.SetQueue; import com.boydti.fawe.util.TaskManager; import com.boydti.fawe.util.WEManager; -import com.intellectualcrafters.plot.object.PseudoRandom; import com.sk89q.jnbt.ByteArrayTag; import com.sk89q.jnbt.IntTag; import com.sk89q.jnbt.NBTInputStream; diff --git a/core/src/main/java/com/boydti/fawe/regions/general/PlotSquaredFeature.java b/core/src/main/java/com/boydti/fawe/regions/general/PlotSquaredFeature.java index a4b694f1..156e6da5 100644 --- a/core/src/main/java/com/boydti/fawe/regions/general/PlotSquaredFeature.java +++ b/core/src/main/java/com/boydti/fawe/regions/general/PlotSquaredFeature.java @@ -4,13 +4,12 @@ import com.boydti.fawe.object.FawePlayer; import com.boydti.fawe.regions.FaweMask; import com.boydti.fawe.regions.FaweMaskManager; import com.intellectualcrafters.plot.PS; -import com.intellectualcrafters.plot.object.Location; -import com.intellectualcrafters.plot.object.Plot; -import com.intellectualcrafters.plot.object.PlotId; import com.intellectualcrafters.plot.object.PlotPlayer; import com.intellectualcrafters.plot.object.RegionWrapper; +import com.plotsquared.listener.WEManager; import com.sk89q.worldedit.BlockVector; import java.util.HashSet; +import org.bukkit.entity.Player; public class PlotSquaredFeature extends FaweMaskManager { public PlotSquaredFeature() { @@ -20,53 +19,33 @@ public class PlotSquaredFeature extends FaweMaskManager { @Override public FaweMask getMask(FawePlayer fp) { - final PlotPlayer pp = PlotPlayer.wrap(fp.parent); - Plot plot = pp.getCurrentPlot(); - Location loc = pp.getLocation(); - final String world = loc.getWorld(); - if (plot == null) { - int min = Integer.MAX_VALUE; - for (final Plot p : pp.getPlots()) { - if (p.getArea().worldname.equals(world)) { - Location bot = p.getBottomAbs(); - Location top = p.getTopAbs(); - Location center = new Location(bot.getWorld(), (bot.getX() + top.getX())/2, 0, (bot.getZ() + top.getZ()) / 2); - final double d = center.getEuclideanDistanceSquared(loc); - if (d < min) { - min = (int) d; - plot = p; - } - } - } + final PlotPlayer pp = PlotPlayer.wrap((Player) fp.parent); + final HashSet regions = WEManager.getMask(pp); + if (regions.size() == 0 || !PS.get().hasPlotArea(pp.getLocation().getWorld())) { + return null; } - if (plot != null) { - final PlotId id = plot.getId(); - if (plot.owner != null) { - if (plot.isOwner(pp.getUUID()) || plot.getTrusted().contains(pp.getUUID()) || (plot.getMembers().contains(pp.getUUID()) && pp.hasPermission("fawe.plotsquared.member"))) { - RegionWrapper region = plot.getLargestRegion(); - HashSet regions = plot.getRegions(); - - final BlockVector pos1 = new BlockVector(region.minX, 0, region.minZ); - final BlockVector pos2 = new BlockVector(region.maxX, 256, region.maxZ); - - final HashSet faweRegions = new HashSet(); - for (final com.intellectualcrafters.plot.object.RegionWrapper current : regions) { - faweRegions.add(new com.boydti.fawe.object.RegionWrapper(current.minX, current.maxX, current.minZ, current.maxZ)); - } - return new FaweMask(pos1, pos2) { - @Override - public String getName() { - return "PLOT^2:" + id; - } - - @Override - public HashSet getRegions() { - return faweRegions; - } - }; - } - } + final HashSet faweRegions = new HashSet<>(); + for (final RegionWrapper current : regions) { + faweRegions.add(new com.boydti.fawe.object.RegionWrapper(current.minX, current.maxX, current.minZ, current.maxZ)); } - return null; + final RegionWrapper region = regions.iterator().next(); + final BlockVector pos1 = new BlockVector(region.minX, 0, region.minZ); + final BlockVector pos2 = new BlockVector(region.maxX, 256, region.maxZ); + return new FaweMask(pos1, pos2) { + @Override + public String getName() { + return "PLOT^2"; + } + + @Override + public boolean contains(BlockVector loc) { + return WEManager.maskContains(regions, loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()); + } + + @Override + public HashSet getRegions() { + return faweRegions; + } + }; } } diff --git a/core/src/main/java/com/boydti/fawe/util/MainUtil.java b/core/src/main/java/com/boydti/fawe/util/MainUtil.java index 4c89f583..f1338644 100644 --- a/core/src/main/java/com/boydti/fawe/util/MainUtil.java +++ b/core/src/main/java/com/boydti/fawe/util/MainUtil.java @@ -17,10 +17,19 @@ import com.sk89q.worldedit.entity.Entity; import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.util.Location; import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.lang.reflect.Array; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; public class MainUtil { /* @@ -65,6 +74,68 @@ public class MainUtil { } } + public static File getJarFile() { + try { + URL url = Fawe.class.getProtectionDomain().getCodeSource().getLocation(); + return new File(new URL(url.toURI().toString().split("\\!")[0].replaceAll("jar:file", "file")).toURI().getPath()); + } catch (MalformedURLException | URISyntaxException | SecurityException e) { + e.printStackTrace(); + return new File(Fawe.imp().getDirectory().getParentFile(), "FastAsyncWorldEdit.jar"); + } + } + + public static File copyFile(File jar, String resource, File output) { + try { + if (output == null) { + output = Fawe.imp().getDirectory(); + } + if (!output.exists()) { + output.mkdirs(); + } + File newFile = new File(output, resource); + if (newFile.exists()) { + return newFile; + } + try (InputStream stream = Fawe.imp().getClass().getResourceAsStream(resource.startsWith("/") ? resource : "/" + resource)) { + byte[] buffer = new byte[2048]; + if (stream == null) { + try (ZipInputStream zis = new ZipInputStream(new FileInputStream(jar))) { + ZipEntry ze = zis.getNextEntry(); + while (ze != null) { + String name = ze.getName(); + if (name.equals(resource)) { + new File(newFile.getParent()).mkdirs(); + try (FileOutputStream fos = new FileOutputStream(newFile)) { + int len; + while ((len = zis.read(buffer)) > 0) { + fos.write(buffer, 0, len); + } + } + ze = null; + } else { + ze = zis.getNextEntry(); + } + } + zis.closeEntry(); + } + return newFile; + } + newFile.createNewFile(); + try (FileOutputStream fos = new FileOutputStream(newFile)) { + int len; + while ((len = stream.read(buffer)) > 0) { + fos.write(buffer, 0, len); + } + } + return newFile; + } + } catch (IOException e) { + e.printStackTrace(); + Fawe.debug("&cCould not save " + resource); + } + return null; + } + public static void sendCompressedMessage(FaweStreamChangeSet set, Actor actor) { try { diff --git a/core/src/main/java/com/sk89q/worldedit/blocks/BlockData.java b/core/src/main/java/com/sk89q/worldedit/blocks/BlockData.java index b20b3063..04a6981a 100644 --- a/core/src/main/java/com/sk89q/worldedit/blocks/BlockData.java +++ b/core/src/main/java/com/sk89q/worldedit/blocks/BlockData.java @@ -72,6 +72,7 @@ public final class BlockData { break; case 203: // BlockId.PURPUR_STAIRS + case BlockID.RED_SANDSTONE_STAIRS: case BlockID.OAK_WOOD_STAIRS: case BlockID.COBBLESTONE_STAIRS: case BlockID.BRICK_STAIRS: @@ -166,6 +167,7 @@ public final class BlockData { } break; } + case 198: // BlockID.END_ROD case BlockID.DISPENSER: case BlockID.DROPPER: int dispPower = data & 0x8; @@ -312,6 +314,8 @@ public final class BlockData { } break; + case 203: // BlockId.PURPUR_STAIRS + case BlockID.RED_SANDSTONE_STAIRS: case BlockID.OAK_WOOD_STAIRS: case BlockID.COBBLESTONE_STAIRS: case BlockID.BRICK_STAIRS: @@ -614,6 +618,8 @@ public final class BlockData { case BlockID.WOODEN_STEP: return data ^ (flipY << 3); + case 203: // BlockId.PURPUR_STAIRS + case BlockID.RED_SANDSTONE_STAIRS: case BlockID.OAK_WOOD_STAIRS: case BlockID.COBBLESTONE_STAIRS: case BlockID.BRICK_STAIRS: @@ -686,6 +692,7 @@ public final class BlockData { } break; + case 198: // BlockID.END_ROD case BlockID.DROPPER: case BlockID.DISPENSER: int dispPower = data & 0x8; @@ -909,6 +916,8 @@ public final class BlockData { if (data < 1 || data > 4) return -1; return mod((data - 1 + increment), 4) + 1; + case 203: // BlockId.PURPUR_STAIRS + case BlockID.RED_SANDSTONE_STAIRS: case BlockID.OAK_WOOD_STAIRS: case BlockID.COBBLESTONE_STAIRS: case BlockID.BRICK_STAIRS: @@ -989,6 +998,7 @@ public final class BlockData { if (withoutFlags < 2 || withoutFlags > 5) return -1; return (mod((withoutFlags - 2 + increment), 4) + 2) | extra; + case 198: // BlockID.END_ROD case BlockID.DISPENSER: case BlockID.DROPPER: store = data & 0x8; diff --git a/core/src/main/java/com/sk89q/worldedit/world/registry/BundledBlockData.java b/core/src/main/java/com/sk89q/worldedit/world/registry/BundledBlockData.java new file mode 100644 index 00000000..f292b164 --- /dev/null +++ b/core/src/main/java/com/sk89q/worldedit/world/registry/BundledBlockData.java @@ -0,0 +1,521 @@ +/* + * 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.world.registry; + +import com.google.common.io.Resources; +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.google.gson.reflect.TypeToken; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.blocks.BlockMaterial; +import java.io.IOException; +import java.lang.reflect.Type; +import java.net.URL; +import java.nio.charset.Charset; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; +import javax.annotation.Nullable; + +/** + * Provides block data based on the built-in block database that is bundled + * with WorldEdit. + * + *

A new instance cannot be created. Use {@link #getInstance()} to get + * an instance.

+ * + *

The data is read from a JSON file that is bundled with WorldEdit. If + * reading fails (which occurs when this class is first instantiated), then + * the methods will return {@code null}s for all blocks.

+ */ +public class BundledBlockData { + + private static final Logger log = Logger.getLogger(BundledBlockData.class.getCanonicalName()); + private static final BundledBlockData INSTANCE = new BundledBlockData(); + + private final Map idMap = new HashMap(); + private final Map legacyMap = new HashMap(); // Trove usage removed temporarily + + /** + * Create a new instance. + */ + private BundledBlockData() { + + } + + /** + * Attempt to load the data from file. + * + * @throws IOException thrown on I/O error + */ + public void loadFromResource() throws IOException { + URL url = WorldEdit.getInstance().getClass().getResource("/com/sk89q/worldedit/world/registry/blocks.json"); + add(url, false); + } + + public void add(URL url, boolean overwrite) throws IOException { + if (url == null) { + throw new IOException("Could not find " + url); + } + GsonBuilder gsonBuilder = new GsonBuilder(); + gsonBuilder.registerTypeAdapter(Vector.class, new FaweVectorAdapter()); + Gson gson = gsonBuilder.create(); + String data = Resources.toString(url, Charset.defaultCharset()); + List entries = gson.fromJson(data, new TypeToken>() {}.getType()); + for (BlockEntry entry : entries) { + add(entry, overwrite); + } + } + + public boolean add(BlockEntry entry, boolean overwrite) { + entry.postDeserialization(); + if (!overwrite && (idMap.containsKey(entry.id) || legacyMap.containsKey(entry.legacyId))) { + return false; + } + idMap.put(entry.id, entry); + legacyMap.put(entry.legacyId, entry); + return true; + } + + /** + * Return the entry for the given block ID. + * + * @param id the ID + * @return the entry, or null + */ + @Nullable + public BlockEntry findById(String id) { + return idMap.get(id); + } + + /** + * Return the entry for the given block legacy numeric ID. + * + * @param id the ID + * @return the entry, or null + */ + @Nullable + public BlockEntry findById(int id) { + return legacyMap.get(id); + } + + /** + * Convert the given string ID to a legacy numeric ID. + * + * @param id the ID + * @return the legacy ID, which may be null if the block does not have a legacy ID + */ + @Nullable + public Integer toLegacyId(String id) { + BlockEntry entry = findById(id); + if (entry != null) { + return entry.legacyId; + } else { + return null; + } + } + + /** + * Get the material properties for the given block. + * + * @param id the legacy numeric ID + * @return the material's properties, or null + */ + @Nullable + public BlockMaterial getMaterialById(int id) { + BlockEntry entry = findById(id); + if (entry != null) { + return entry.material; + } else { + return null; + } + } + + /** + * Get the states for the given block. + * + * @param id the legacy numeric ID + * @return the block's states, or null if no information is available + */ + @Nullable + public Map getStatesById(int id) { + BlockEntry entry = findById(id); + if (entry != null) { + return entry.states; + } else { + return null; + } + } + + /** + * Get a singleton instance of this object. + * + * @return the instance + */ + public static BundledBlockData getInstance() { + return INSTANCE; + } + + public static class BlockEntry { + private int legacyId; + private String id; + private String unlocalizedName; + private List aliases; + private Map states = new HashMap(); + private FaweBlockMaterial material = new FaweBlockMaterial(); + + void postDeserialization() { + for (FaweState state : states.values()) { + state.postDeserialization(); + } + } + } + + public static Class inject() { + return BundledBlockData.class; + } + + public class FaweVectorAdapter implements JsonDeserializer { + + @Override + public Vector deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + JsonArray jsonArray = json.getAsJsonArray(); + if (jsonArray.size() != 3) { + throw new JsonParseException("Expected array of 3 length for Vector"); + } + + double x = jsonArray.get(0).getAsDouble(); + double y = jsonArray.get(1).getAsDouble(); + double z = jsonArray.get(2).getAsDouble(); + + return new Vector(x, y, z); + } + } + + class FaweStateValue implements StateValue { + + private FaweState state; + private Byte data; + private Vector direction; + + void setState(FaweState state) { + this.state = state; + } + + @Override + public boolean isSet(BaseBlock block) { + return data != null && (block.getData() & state.getDataMask()) == data; + } + + @Override + public boolean set(BaseBlock block) { + if (data != null) { + block.setData((block.getData() & ~state.getDataMask()) | data); + return true; + } else { + return false; + } + } + + @Override + public Vector getDirection() { + return direction; + } + + } + + class FaweState implements State { + + private Byte dataMask; + private Map values; + + @Override + public Map valueMap() { + return Collections.unmodifiableMap(values); + } + + @Nullable + @Override + public StateValue getValue(BaseBlock block) { + for (StateValue value : values.values()) { + if (value.isSet(block)) { + return value; + } + } + + return null; + } + + byte getDataMask() { + return dataMask != null ? dataMask : 0xF; + } + + @Override + public boolean hasDirection() { + for (FaweStateValue value : values.values()) { + if (value.getDirection() != null) { + return true; + } + } + + return false; + } + + void postDeserialization() { + for (FaweStateValue v : values.values()) { + v.setState(this); + } + } + + } + + public static class FaweBlockMaterial implements BlockMaterial { + + private boolean renderedAsNormalBlock; + private boolean fullCube; + private boolean opaque; + private boolean powerSource; + private boolean liquid; + private boolean solid; + private float hardness; + private float resistance; + private float slipperiness; + private boolean grassBlocking; + private float ambientOcclusionLightValue; + private int lightOpacity; + private int lightValue; + private boolean fragileWhenPushed; + private boolean unpushable; + private boolean adventureModeExempt; + private boolean ticksRandomly; + private boolean usingNeighborLight; + private boolean movementBlocker; + private boolean burnable; + private boolean toolRequired; + private boolean replacedDuringPlacement; + + @Override + public boolean isRenderedAsNormalBlock() { + return renderedAsNormalBlock; + } + + public void setRenderedAsNormalBlock(boolean renderedAsNormalBlock) { + this.renderedAsNormalBlock = renderedAsNormalBlock; + } + + @Override + public boolean isFullCube() { + return fullCube; + } + + public void setFullCube(boolean fullCube) { + this.fullCube = fullCube; + } + + @Override + public boolean isOpaque() { + return opaque; + } + + public void setOpaque(boolean opaque) { + this.opaque = opaque; + } + + @Override + public boolean isPowerSource() { + return powerSource; + } + + public void setPowerSource(boolean powerSource) { + this.powerSource = powerSource; + } + + @Override + public boolean isLiquid() { + return liquid; + } + + public void setLiquid(boolean liquid) { + this.liquid = liquid; + } + + @Override + public boolean isSolid() { + return solid; + } + + public void setSolid(boolean solid) { + this.solid = solid; + } + + @Override + public float getHardness() { + return hardness; + } + + public void setHardness(float hardness) { + this.hardness = hardness; + } + + @Override + public float getResistance() { + return resistance; + } + + public void setResistance(float resistance) { + this.resistance = resistance; + } + + @Override + public float getSlipperiness() { + return slipperiness; + } + + public void setSlipperiness(float slipperiness) { + this.slipperiness = slipperiness; + } + + @Override + public boolean isGrassBlocking() { + return grassBlocking; + } + + public void setGrassBlocking(boolean grassBlocking) { + this.grassBlocking = grassBlocking; + } + + @Override + public float getAmbientOcclusionLightValue() { + return ambientOcclusionLightValue; + } + + public void setAmbientOcclusionLightValue(float ambientOcclusionLightValue) { + this.ambientOcclusionLightValue = ambientOcclusionLightValue; + } + + @Override + public int getLightOpacity() { + return lightOpacity; + } + + public void setLightOpacity(int lightOpacity) { + this.lightOpacity = lightOpacity; + } + + @Override + public int getLightValue() { + return lightValue; + } + + public void setLightValue(int lightValue) { + this.lightValue = lightValue; + } + + @Override + public boolean isFragileWhenPushed() { + return fragileWhenPushed; + } + + public void setFragileWhenPushed(boolean fragileWhenPushed) { + this.fragileWhenPushed = fragileWhenPushed; + } + + @Override + public boolean isUnpushable() { + return unpushable; + } + + public void setUnpushable(boolean unpushable) { + this.unpushable = unpushable; + } + + @Override + public boolean isAdventureModeExempt() { + return adventureModeExempt; + } + + public void setAdventureModeExempt(boolean adventureModeExempt) { + this.adventureModeExempt = adventureModeExempt; + } + + @Override + public boolean isTicksRandomly() { + return ticksRandomly; + } + + public void setTicksRandomly(boolean ticksRandomly) { + this.ticksRandomly = ticksRandomly; + } + + @Override + public boolean isUsingNeighborLight() { + return usingNeighborLight; + } + + public void setUsingNeighborLight(boolean usingNeighborLight) { + this.usingNeighborLight = usingNeighborLight; + } + + @Override + public boolean isMovementBlocker() { + return movementBlocker; + } + + public void setMovementBlocker(boolean movementBlocker) { + this.movementBlocker = movementBlocker; + } + + @Override + public boolean isBurnable() { + return burnable; + } + + public void setBurnable(boolean burnable) { + this.burnable = burnable; + } + + @Override + public boolean isToolRequired() { + return toolRequired; + } + + public void setToolRequired(boolean toolRequired) { + this.toolRequired = toolRequired; + } + + @Override + public boolean isReplacedDuringPlacement() { + return replacedDuringPlacement; + } + + public void setReplacedDuringPlacement(boolean replacedDuringPlacement) { + this.replacedDuringPlacement = replacedDuringPlacement; + } + } +} \ No newline at end of file diff --git a/core/src/main/resources/extrablocks.json b/core/src/main/resources/extrablocks.json new file mode 100644 index 00000000..ca763401 --- /dev/null +++ b/core/src/main/resources/extrablocks.json @@ -0,0 +1,2 @@ +// Add extra blocks here +// See: https://github.com/sk89q/WorldEdit/blob/master/worldedit-core/src/main/resources/com/sk89q/worldedit/world/registry/blocks.json \ No newline at end of file