From 17fb559f104a440188317816a253932ae9daf1cf Mon Sep 17 00:00:00 2001 From: Jesse Boyd Date: Fri, 28 Apr 2017 05:28:03 +1000 Subject: [PATCH] Various FAWE texture support - Put any mods or client jars in `FastAsyncWorldEdit/textures` - These textures can be used in patterns and commands (WIP) - Adds #color:color pattern Added random flip to #fullcopy::: Improved parsing for clipboard loading FRB now only works in regions you are the owner of (or if you have admin bypass) --- .gitignore | 3 +- .../fawe/bukkit/v1_11/FaweChunkLoader.java | 243 ++++++++++++++ core/src/main/java/com/boydti/fawe/Fawe.java | 23 +- .../java/com/boydti/fawe/config/Settings.java | 4 + .../fawe/jnbt/anvil/generator/SchemGen.java | 6 +- .../heightmap/AverageHeightMapFilter.java | 48 +++ .../object/changeset/DiskStorageHistory.java | 8 +- .../fawe/object/changeset/FaweChangeSet.java | 13 +- .../clipboard/MultiClipboardHolder.java | 12 +- .../pattern/RandomFullClipboardPattern.java | 17 +- .../com/boydti/fawe/util/TextureUtil.java | 297 +++++++++++++++++- .../worldedit/command/HistoryCommands.java | 18 +- .../worldedit/command/SchematicCommands.java | 11 +- .../factory/HashTagPatternParser.java | 33 +- .../extension/platform/PlatformManager.java | 1 - .../extent/clipboard/io/ClipboardFormat.java | 6 + .../worldedit/math/convolution/HeightMap.java | 10 + .../world/registry/BundledBlockData.java | 53 +++- core/src/main/resources/extrablocks.json | 9 +- 19 files changed, 760 insertions(+), 55 deletions(-) create mode 100644 bukkit/src/main/java/com/boydti/fawe/bukkit/v1_11/FaweChunkLoader.java create mode 100644 core/src/main/java/com/boydti/fawe/object/brush/heightmap/AverageHeightMapFilter.java diff --git a/.gitignore b/.gitignore index de7cc817..ac3619cc 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,5 @@ gradle.log build /mvn spigot-1.10 -wiki_permissions.md \ No newline at end of file +wiki_permissions.md +/textures \ No newline at end of file diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_11/FaweChunkLoader.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_11/FaweChunkLoader.java new file mode 100644 index 00000000..b370d83d --- /dev/null +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_11/FaweChunkLoader.java @@ -0,0 +1,243 @@ +package com.boydti.fawe.bukkit.v1_11; + +import com.boydti.fawe.object.FaweInputStream; +import com.boydti.fawe.util.MainUtil; +import com.boydti.fawe.util.MathMan; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Iterator; +import java.util.List; +import javax.annotation.Nullable; +import net.minecraft.server.v1_11_R1.Block; +import net.minecraft.server.v1_11_R1.Chunk; +import net.minecraft.server.v1_11_R1.ChunkSection; +import net.minecraft.server.v1_11_R1.Entity; +import net.minecraft.server.v1_11_R1.ExceptionWorldConflict; +import net.minecraft.server.v1_11_R1.IAsyncChunkSaver; +import net.minecraft.server.v1_11_R1.IChunkLoader; +import net.minecraft.server.v1_11_R1.MinecraftKey; +import net.minecraft.server.v1_11_R1.NBTCompressedStreamTools; +import net.minecraft.server.v1_11_R1.NBTReadLimiter; +import net.minecraft.server.v1_11_R1.NBTTagCompound; +import net.minecraft.server.v1_11_R1.NBTTagList; +import net.minecraft.server.v1_11_R1.NextTickListEntry; +import net.minecraft.server.v1_11_R1.NibbleArray; +import net.minecraft.server.v1_11_R1.TileEntity; +import net.minecraft.server.v1_11_R1.World; + +public class FaweChunkLoader implements IChunkLoader, IAsyncChunkSaver { + + private final File folder; + private Long2ObjectMap hashes = new Long2ObjectOpenHashMap<>(); + + public FaweChunkLoader(File folder) { + this.folder = folder; + System.out.println(folder); + } + + // writeNextIO (save) + @Override + public boolean c() { + return false; + } + + // loadChunk + @Nullable + @Override + public Chunk a(World world, int x, int z) throws IOException { + long pair = MathMan.pairInt(x, z); + Long hash = hashes.get(pair); + if (hash == null) { + return null; + } + File file = new File(folder, hash.toString()); + int length = (int) file.length(); + try (FaweInputStream in = MainUtil.getCompressedIS(new FileInputStream(file), Math.min(length, 8192))) { + NBTTagCompound nbttagcompound = NBTCompressedStreamTools.a(in, NBTReadLimiter.a); + return readChunkFromNBT(world, nbttagcompound); + } + } + + private Chunk readChunkFromNBT(World world, NBTTagCompound nbttagcompound) { + int i = nbttagcompound.getInt("xPos"); + int j = nbttagcompound.getInt("zPos"); + Chunk chunk = new Chunk(world, i, j); + chunk.a(nbttagcompound.getIntArray("HeightMap")); + chunk.d(nbttagcompound.getBoolean("TerrainPopulated")); + chunk.e(nbttagcompound.getBoolean("LightPopulated")); + chunk.c(nbttagcompound.getLong("InhabitedTime")); + NBTTagList nbttaglist = nbttagcompound.getList("Sections", 10); + ChunkSection[] achunksection = new ChunkSection[16]; + boolean flag1 = world.worldProvider.m(); + + for(int k = 0; k < nbttaglist.size(); ++k) { + NBTTagCompound nbttagcompound1 = nbttaglist.get(k); + byte b0 = nbttagcompound1.getByte("Y"); + ChunkSection chunksection = new ChunkSection(b0 << 4, flag1); + byte[] abyte = nbttagcompound1.getByteArray("Blocks"); + NibbleArray nibblearray = new NibbleArray(nbttagcompound1.getByteArray("Data")); + NibbleArray nibblearray1 = nbttagcompound1.hasKeyOfType("Add", 7) ? new NibbleArray(nbttagcompound1.getByteArray("Add")):null; + chunksection.getBlocks().a(abyte, nibblearray, nibblearray1); + chunksection.a(new NibbleArray(nbttagcompound1.getByteArray("BlockLight"))); + if(flag1) { + chunksection.b(new NibbleArray(nbttagcompound1.getByteArray("SkyLight"))); + } + + chunksection.recalcBlockCounts(); + achunksection[b0] = chunksection; + } + + chunk.a(achunksection); + if(nbttagcompound.hasKeyOfType("Biomes", 7)) { + chunk.a(nbttagcompound.getByteArray("Biomes")); + } + + return chunk; + } + + + // saveChunk + @Override + public void a(World world, Chunk chunk) throws IOException, ExceptionWorldConflict { + try { + NBTTagCompound exception = new NBTTagCompound(); + NBTTagCompound nbttagcompound1 = new NBTTagCompound(); + exception.set("Level", nbttagcompound1); + exception.setInt("DataVersion", 819); + this.writeChunkToNBT(chunk, world, nbttagcompound1); +// this.a(chunk.k(), exception); + } catch (Exception var5) { + } + } + + private void writeChunkToNBT(Chunk chunk, World world, NBTTagCompound nbttagcompound) { + nbttagcompound.setInt("xPos", chunk.locX); + nbttagcompound.setInt("zPos", chunk.locZ); + nbttagcompound.setLong("LastUpdate", world.getTime()); + nbttagcompound.setIntArray("HeightMap", chunk.r()); + nbttagcompound.setBoolean("TerrainPopulated", chunk.isDone()); + nbttagcompound.setBoolean("LightPopulated", chunk.v()); + nbttagcompound.setLong("InhabitedTime", chunk.x()); + ChunkSection[] achunksection = chunk.getSections(); + NBTTagList nbttaglist = new NBTTagList(); + boolean flag = world.worldProvider.m(); + ChunkSection[] achunksection1 = achunksection; + int i = achunksection.length; + + NBTTagCompound nbttagcompound1; + for(int nbttaglist1 = 0; nbttaglist1 < i; ++nbttaglist1) { + ChunkSection iterator = achunksection1[nbttaglist1]; + if(iterator != Chunk.a) { + nbttagcompound1 = new NBTTagCompound(); + nbttagcompound1.setByte("Y", (byte)(iterator.getYPosition() >> 4 & 255)); + byte[] nbttaglist2 = new byte[4096]; + NibbleArray list = new NibbleArray(); + NibbleArray nibblearray1 = iterator.getBlocks().exportData(nbttaglist2, list); + nbttagcompound1.setByteArray("Blocks", nbttaglist2); + nbttagcompound1.setByteArray("Data", list.asBytes()); + if(nibblearray1 != null) { + nbttagcompound1.setByteArray("Add", nibblearray1.asBytes()); + } + + nbttagcompound1.setByteArray("BlockLight", iterator.getEmittedLightArray().asBytes()); + if(flag) { + nbttagcompound1.setByteArray("SkyLight", iterator.getSkyLightArray().asBytes()); + } else { + nbttagcompound1.setByteArray("SkyLight", new byte[iterator.getEmittedLightArray().asBytes().length]); + } + + nbttaglist.add(nbttagcompound1); + } + } + + nbttagcompound.set("Sections", nbttaglist); + nbttagcompound.setByteArray("Biomes", chunk.getBiomeIndex()); + chunk.g(false); + NBTTagList var22 = new NBTTagList(); + + Iterator var23; + for(i = 0; i < chunk.getEntitySlices().length; ++i) { + var23 = chunk.getEntitySlices()[i].iterator(); + + while(var23.hasNext()) { + Entity var24 = (Entity)var23.next(); + nbttagcompound1 = new NBTTagCompound(); + if(var24.d(nbttagcompound1)) { + chunk.g(true); + var22.add(nbttagcompound1); + } + } + } + + nbttagcompound.set("Entities", var22); + NBTTagList var25 = new NBTTagList(); + var23 = chunk.getTileEntities().values().iterator(); + + while(var23.hasNext()) { + TileEntity var26 = (TileEntity)var23.next(); + nbttagcompound1 = var26.save(new NBTTagCompound()); + var25.add(nbttagcompound1); + } + + nbttagcompound.set("TileEntities", var25); + List var27 = world.a(chunk, false); + if(var27 != null) { + long k = world.getTime(); + NBTTagList nbttaglist3 = new NBTTagList(); + Iterator iterator1 = var27.iterator(); + + while(iterator1.hasNext()) { + NextTickListEntry nextticklistentry = (NextTickListEntry)iterator1.next(); + NBTTagCompound nbttagcompound2 = new NBTTagCompound(); + MinecraftKey minecraftkey = (MinecraftKey) Block.REGISTRY.b(nextticklistentry.a()); + nbttagcompound2.setString("i", minecraftkey == null?"":minecraftkey.toString()); + nbttagcompound2.setInt("x", nextticklistentry.a.getX()); + nbttagcompound2.setInt("y", nextticklistentry.a.getY()); + nbttagcompound2.setInt("z", nextticklistentry.a.getZ()); + nbttagcompound2.setInt("t", (int)(nextticklistentry.b - k)); + nbttagcompound2.setInt("p", nextticklistentry.c); + nbttaglist3.add(nbttagcompound2); + } + + nbttagcompound.set("TileTicks", nbttaglist3); + } + + } + + // saveExtraChunkData + @Override + public void b(World world, Chunk chunk) throws IOException { + + } + + // chunkTick + @Override + public void a() { + + } + + // saveExtraData + @Override + public void b() { +// try { +// this.savingExtraData = true; +// +// while(true) { +// if(this.writeNextIO()) { +// continue; +// } +// } +// } finally { +// this.savingExtraData = false; +// } + } + + // isChunkGeneratedAt + @Override + public boolean a(int x, int z) { + return hashes.containsKey(MathMan.pairInt(x, z)); + } +} diff --git a/core/src/main/java/com/boydti/fawe/Fawe.java b/core/src/main/java/com/boydti/fawe/Fawe.java index fe39a454..aa50a32d 100644 --- a/core/src/main/java/com/boydti/fawe/Fawe.java +++ b/core/src/main/java/com/boydti/fawe/Fawe.java @@ -12,6 +12,7 @@ import com.boydti.fawe.util.MainUtil; import com.boydti.fawe.util.MemUtil; import com.boydti.fawe.util.StringMan; import com.boydti.fawe.util.TaskManager; +import com.boydti.fawe.util.TextureUtil; import com.boydti.fawe.util.Updater; import com.boydti.fawe.util.WEManager; import com.sk89q.jnbt.NBTInputStream; @@ -184,6 +185,7 @@ public class Fawe { private FaweVersion version; private VisualQueue visualQueue; private Updater updater; + private TextureUtil textures; /** * Get the implementation specific class @@ -272,7 +274,6 @@ public class Fawe { WEManager.IMP.managers.add(new PlotSquaredFeature()); Fawe.debug("Plugin 'PlotSquared' found. Using it now."); } catch (Throwable e) {} - Fawe.this.worldedit = WorldEdit.getInstance(); } }, 0); @@ -319,6 +320,19 @@ public class Fawe { return updater; } + public TextureUtil getTextureUtil() { + TextureUtil tmp = textures; + if (tmp == null) { + synchronized (this) { + tmp = textures; + if (tmp == null) { + textures = tmp = new TextureUtil(); + } + } + } + return tmp; + } + /** * The FaweTimer is a useful class for monitoring TPS * @return FaweTimer @@ -372,13 +386,8 @@ public class Fawe { BBC.load(new File(this.IMP.getDirectory(), "message.yml")); } - private WorldEdit worldedit; - public WorldEdit getWorldEdit() { - if (this.worldedit == null) { - return worldedit = WorldEdit.getInstance(); - } - return this.worldedit; + return WorldEdit.getInstance(); } public static void setupInjector() { diff --git a/core/src/main/java/com/boydti/fawe/config/Settings.java b/core/src/main/java/com/boydti/fawe/config/Settings.java index f35b0673..dacd07f9 100644 --- a/core/src/main/java/com/boydti/fawe/config/Settings.java +++ b/core/src/main/java/com/boydti/fawe/config/Settings.java @@ -68,6 +68,10 @@ public class Settings extends Config { @Comment("Paths for various directories") public static final class PATHS { + @Comment({ + "Put any minecraft or mod jars for FAWE to be aware of block textures", + }) + public String TEXTURES = "textures"; public String HISTORY = "history"; public String CLIPBOARD = "clipboard"; @Comment("Each player has their own sub directory for schematics") diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/generator/SchemGen.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/generator/SchemGen.java index 6142c1f5..debd9202 100644 --- a/core/src/main/java/com/boydti/fawe/jnbt/anvil/generator/SchemGen.java +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/generator/SchemGen.java @@ -8,6 +8,7 @@ import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.math.transform.AffineTransform; +import com.sk89q.worldedit.math.transform.Transform; import com.sk89q.worldedit.session.ClipboardHolder; import com.sk89q.worldedit.world.registry.WorldData; @@ -45,10 +46,11 @@ public class SchemGen extends Resource { } Clipboard clipboard = holder.getClipboard(); Schematic schematic = new Schematic(clipboard); - if (holder.getTransform().isIdentity()) { + Transform transform = holder.getTransform(); + if (transform.isIdentity()) { schematic.paste(extent, mutable, false); } else { - schematic.paste(extent, worldData, mutable, false, holder.getTransform()); + schematic.paste(extent, worldData, mutable, false, transform); } mutable.mutY(y); return true; diff --git a/core/src/main/java/com/boydti/fawe/object/brush/heightmap/AverageHeightMapFilter.java b/core/src/main/java/com/boydti/fawe/object/brush/heightmap/AverageHeightMapFilter.java new file mode 100644 index 00000000..717cdd81 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/brush/heightmap/AverageHeightMapFilter.java @@ -0,0 +1,48 @@ +package com.boydti.fawe.object.brush.heightmap; + +public class AverageHeightMapFilter { + private int[] inData; + private int[] buffer; + private final int width; + private final int height; + private final int minY; + private final int maxY; + + public AverageHeightMapFilter(int[] inData, int width, int height, int minY, int maxY) { + this.inData = inData; + this.width = width; + this.height = height; + this.minY = minY; + this.maxY = maxY; + this.buffer = new int[inData.length]; + } + public int[] filter(int iterations) { + for (int j = 0; j < iterations; j++) { + int a = -width; + int b = width; + int c = 1; + int d = -1; + for (int i = 0; i < inData.length; i++, a++, b++, c++, d++) { + int height = inData[i]; + if (height < minY || height > maxY) { + buffer[i] = height; + continue; + } + int average = (2 + get(a, height) + get(b, height) + get(c, height) + get(d, height)) >> 2; + buffer[i] = average; + } + int[] tmp = inData; + inData = buffer; + buffer = tmp; + } + return inData; + } + + private int get(int index, int def) { + int val = inData[Math.max(0, Math.min(inData.length - 1, index))]; + if (val < minY || val > maxY) { + return def; + } + return val; + } +} diff --git a/core/src/main/java/com/boydti/fawe/object/changeset/DiskStorageHistory.java b/core/src/main/java/com/boydti/fawe/object/changeset/DiskStorageHistory.java index 1b11f3fc..996ef11b 100644 --- a/core/src/main/java/com/boydti/fawe/object/changeset/DiskStorageHistory.java +++ b/core/src/main/java/com/boydti/fawe/object/changeset/DiskStorageHistory.java @@ -135,12 +135,16 @@ public class DiskStorageHistory extends FaweStreamChangeSet { enttFile.delete(); } - public void undo(FawePlayer fp) { - EditSession session = toEditSession(fp); + public void undo(FawePlayer fp, RegionWrapper[] regions) { + EditSession session = toEditSession(fp, regions); session.undo(session); deleteFiles(); } + public void undo(FawePlayer fp) { + undo(fp, null); + } + public UUID getUUID() { return uuid; } diff --git a/core/src/main/java/com/boydti/fawe/object/changeset/FaweChangeSet.java b/core/src/main/java/com/boydti/fawe/object/changeset/FaweChangeSet.java index f446627a..998dfd52 100644 --- a/core/src/main/java/com/boydti/fawe/object/changeset/FaweChangeSet.java +++ b/core/src/main/java/com/boydti/fawe/object/changeset/FaweChangeSet.java @@ -7,6 +7,7 @@ import com.boydti.fawe.config.Settings; import com.boydti.fawe.object.FaweChunk; import com.boydti.fawe.object.FawePlayer; import com.boydti.fawe.object.FaweQueue; +import com.boydti.fawe.object.RegionWrapper; import com.boydti.fawe.object.RunnableVal2; import com.boydti.fawe.util.EditSessionBuilder; import com.boydti.fawe.util.MainUtil; @@ -138,7 +139,17 @@ public abstract class FaweChangeSet implements ChangeSet { public void delete() {}; public EditSession toEditSession(FawePlayer player) { - EditSession editSession = new EditSessionBuilder(getWorld()).player(player).autoQueue(false).fastmode(false).checkMemory(false).changeSet(this).limitUnlimited().allowedRegionsEverywhere().build(); + return toEditSession(player, null); + } + + public EditSession toEditSession(FawePlayer player, RegionWrapper[] regions) { + EditSessionBuilder builder = new EditSessionBuilder(getWorld()).player(player).autoQueue(false).fastmode(false).checkMemory(false).changeSet(this).limitUnlimited(); + if (regions != null) { + builder.allowedRegions(regions); + } else { + builder.allowedRegionsEverywhere(); + } + EditSession editSession = builder.build(); editSession.setSize(1); return editSession; } diff --git a/core/src/main/java/com/boydti/fawe/object/clipboard/MultiClipboardHolder.java b/core/src/main/java/com/boydti/fawe/object/clipboard/MultiClipboardHolder.java index 69ff9de6..da77406c 100644 --- a/core/src/main/java/com/boydti/fawe/object/clipboard/MultiClipboardHolder.java +++ b/core/src/main/java/com/boydti/fawe/object/clipboard/MultiClipboardHolder.java @@ -1,10 +1,10 @@ package com.boydti.fawe.object.clipboard; -import com.boydti.fawe.object.PseudoRandom; -import com.sk89q.worldedit.extent.clipboard.Clipboard; -import com.sk89q.worldedit.math.transform.Transform; -import com.sk89q.worldedit.session.ClipboardHolder; -import com.sk89q.worldedit.world.registry.WorldData; + import com.boydti.fawe.object.PseudoRandom; + import com.sk89q.worldedit.extent.clipboard.Clipboard; + import com.sk89q.worldedit.math.transform.Transform; + import com.sk89q.worldedit.session.ClipboardHolder; + import com.sk89q.worldedit.world.registry.WorldData; public class MultiClipboardHolder extends ClipboardHolder{ private final ClipboardHolder[] holders; @@ -41,4 +41,4 @@ public class MultiClipboardHolder extends ClipboardHolder{ if (holder != null) holder.close(); } } -} +} \ No newline at end of file diff --git a/core/src/main/java/com/boydti/fawe/object/pattern/RandomFullClipboardPattern.java b/core/src/main/java/com/boydti/fawe/object/pattern/RandomFullClipboardPattern.java index 09ce1ea8..99ba3a07 100644 --- a/core/src/main/java/com/boydti/fawe/object/pattern/RandomFullClipboardPattern.java +++ b/core/src/main/java/com/boydti/fawe/object/pattern/RandomFullClipboardPattern.java @@ -10,6 +10,7 @@ import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.function.pattern.AbstractPattern; import com.sk89q.worldedit.math.transform.AffineTransform; +import com.sk89q.worldedit.math.transform.Transform; import com.sk89q.worldedit.session.ClipboardHolder; import com.sk89q.worldedit.world.registry.WorldData; @@ -21,9 +22,10 @@ public class RandomFullClipboardPattern extends AbstractPattern { private final ClipboardHolder[] clipboards; private final MutableBlockVector mutable = new MutableBlockVector(); private boolean randomRotate; + private boolean randomFlip; private WorldData worldData; - public RandomFullClipboardPattern(Extent extent, WorldData worldData, ClipboardHolder[] clipboards, boolean randomRotate) { + public RandomFullClipboardPattern(Extent extent, WorldData worldData, ClipboardHolder[] clipboards, boolean randomRotate, boolean randomFlip) { checkNotNull(clipboards); this.clipboards = clipboards; this.extent = extent; @@ -34,15 +36,24 @@ public class RandomFullClipboardPattern extends AbstractPattern { @Override public boolean apply(Extent extent, Vector setPosition, Vector getPosition) throws WorldEditException { ClipboardHolder holder = clipboards[PseudoRandom.random.random(clipboards.length)]; + AffineTransform transform = new AffineTransform(); if (randomRotate) { + transform = transform.rotateY(PseudoRandom.random.random(4) * 90); holder.setTransform(new AffineTransform().rotateY(PseudoRandom.random.random(4) * 90)); } + if (randomFlip) { + transform = transform.scale(new Vector(1, 0, 0).multiply(-2).add(1, 1, 1)); + } + if (!transform.isIdentity()) { + holder.setTransform(transform); + } Clipboard clipboard = holder.getClipboard(); Schematic schematic = new Schematic(clipboard); - if (holder.getTransform().isIdentity()) { + Transform newTransform = holder.getTransform(); + if (newTransform.isIdentity()) { schematic.paste(extent, setPosition, false); } else { - schematic.paste(extent, worldData, setPosition, false, holder.getTransform()); + schematic.paste(extent, worldData, setPosition, false, newTransform); } return true; } diff --git a/core/src/main/java/com/boydti/fawe/util/TextureUtil.java b/core/src/main/java/com/boydti/fawe/util/TextureUtil.java index 543ba61e..233f00cb 100644 --- a/core/src/main/java/com/boydti/fawe/util/TextureUtil.java +++ b/core/src/main/java/com/boydti/fawe/util/TextureUtil.java @@ -1,28 +1,313 @@ package com.boydti.fawe.util; +import com.boydti.fawe.Fawe; +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.config.Settings; +import com.sk89q.worldedit.blocks.BaseBlock; import com.sk89q.worldedit.world.registry.BundledBlockData; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import java.awt.Color; +import java.awt.image.BufferedImage; import java.io.File; import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import javax.imageio.ImageIO; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; public class TextureUtil { private final File folder; - private Color[] colors; - public TextureUtil(File folder) { + private int[] blockColors = new int[Character.MAX_VALUE + 1]; + private int[] validColors; + private int[] validBlockIds; + + private String getFileName(String path) { + String[] split = path.toString().split("[/|\\\\]"); + String name = split[split.length - 1]; + int dot = name.indexOf('.'); + if (dot != -1) { + name = name.substring(0, dot); + } + return name; + } + + private String alphabetize(String asset) { + String[] split = asset.split("_"); + Arrays.sort(split); + return StringMan.join(split, "_"); + } + + /** + * Unfortunately the names used for the textures don't match the block id
+ * - This will try to guess possible relevant block ids
+ * - Match some by reformatting
+ * - Match by reordering
+ * - Match by appending / removing
+ * - Match by hardcoded values
+ */ + private void addTextureNames(String modelName, JSONObject root, Map texturesMap) { + JSONObject textures = (JSONObject) root.get("textures"); + if (textures == null) { + return; + } + Set names = new HashSet<>(); + String all = (String) textures.get("all"); + if (all != null) { + String textureName = getFileName(all); + // Add the model + names.add(modelName); + names.add(textureName); + for (String name : new ArrayList<>(names)) { + if (name.contains("big_oak")) { + name = name.replaceAll("big_oak", "oak"); + names.add(name); + } + String[] split = name.split("_"); + switch (split[0]) { + case "glass": + names.add(name.replaceAll("glass_", "stained_glass_")); + break; + case "log": + names.add(name.replaceAll("log_", "log2_")); + break; + case "leaves": + names.add(name.replaceAll("leaves_", "leaves2_")); + case "mushroom": + names.add(name.replaceAll("mushroom_block_skin_", "mushroom_block_")); + default: + continue; + } + } + for (String name : names) { + texturesMap.putIfAbsent(name, textureName); + } + } else { + if (textures.containsKey("side") && textures.containsKey("end") && !textures.containsKey("bottom") && !textures.containsKey("top") && !textures.containsKey("platform")) { + String side = (String) textures.get("side"); + } else if (textures.containsKey("up")) { + // TODO: Just mushroom, not super important + String up = (String) textures.get("up"); + } + } + } + + public TextureUtil() throws IOException, ParseException { + this(MainUtil.getFile(Fawe.imp().getDirectory(), Settings.IMP.PATHS.TEXTURES)); + } + + public TextureUtil(File folder) throws IOException, ParseException { this.folder = folder; - BundledBlockData bundled = BundledBlockData.getInstance(); + loadModTextures(); + } + + public void loadModTextures() throws IOException, ParseException { + if (!folder.exists()) { + return; + } for (File file : folder.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { - return name.endsWith(".png"); + return name.endsWith(".jar"); } })) { - String name = file.getName().split("\\.")[0]; + ZipFile zipFile = new ZipFile(file); + // get mods + BundledBlockData bundled = BundledBlockData.getInstance(); + bundled.loadFromResource(); + + Set mods = new HashSet(); + { + Enumeration entries = zipFile.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + String name = entry.getName(); + Path path = Paths.get(name); + if (path.startsWith("assets" + File.separator)) { + String[] split = path.toString().split(Pattern.quote(File.separator)); + if (split.length > 1) { + String modId = split[1]; + if (mods.add(modId)) { + } + } + } + continue; + } + } + Int2ObjectOpenHashMap colorMap = new Int2ObjectOpenHashMap<>(); + for (String modId : mods) { + String modelsDir = "assets" + "/" + modId + "/" + "models" + "/" + "block"; + String texturesDir = "assets" + "/" + modId + "/" + "textures" + "/" + "blocks"; + Map texturesMap = new ConcurrentHashMap<>(); + // Read models + { + // Read .json + // Find texture file + Enumeration entries = zipFile.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + if (entry.isDirectory()) { + continue; + } + String name = entry.getName(); + if (!name.endsWith(".json")) { + continue; + } + Path path = Paths.get(name); + if (path.startsWith(modelsDir)) { + String[] split = path.toString().split("[/|\\\\|\\.]"); + String blockName = getFileName(path.toString()); + // Ignore special models + if (blockName.startsWith("#")) { + continue; + } + try (InputStream is = zipFile.getInputStream(entry)) { //Read from a file, or a HttpRequest, or whatever. + JSONParser parser = new JSONParser(); + JSONObject root = (JSONObject) parser.parse(new InputStreamReader(is, "UTF-8")); + addTextureNames(blockName, root, texturesMap); + } + } + } + } + for (String key : new ArrayList<>(texturesMap.keySet())) { + String value = texturesMap.get(key); + texturesMap.put(alphabetize(key), value); + String[] split = key.split("_"); + if (split.length > 1) { + key = StringMan.join(Arrays.copyOfRange(split, 0, split.length - 1), "_"); + texturesMap.putIfAbsent(key, value); + } + } + Int2ObjectOpenHashMap idMap = new Int2ObjectOpenHashMap<>(); + for (String id : bundled.stateMap.keySet()) { + if (id.startsWith(modId)) { + BaseBlock block = bundled.findByState(id); + id = id.substring(modId.length() + 1).replaceAll(":", "_"); + String texture = texturesMap.remove(id); + if (texture == null) { + texture = texturesMap.remove(alphabetize(id)); + } + if (texture != null) { + idMap.put(block.getCombined(), texture); + } + } + } + { + for (Int2ObjectMap.Entry entry : idMap.int2ObjectEntrySet()) { + int combined = entry.getIntKey(); + String path = texturesDir + "/" + entry.getValue() + ".png"; + ZipEntry textureEntry = zipFile.getEntry(path); + try (InputStream is = zipFile.getInputStream(textureEntry)) { + BufferedImage image = ImageIO.read(is); + int color = getColor(image); + colorMap.put((int) combined, (Integer) color); + } + } + // Load and map the textures + // + } + } + validBlockIds = new int[colorMap.size()]; + validColors = new int[colorMap.size()]; + Arrays.fill(blockColors, 0); + int index = 0; + for (Int2ObjectMap.Entry entry : colorMap.int2ObjectEntrySet()) { + int combinedId = entry.getIntKey(); + int color = entry.getValue(); + blockColors[combinedId] = color; + validBlockIds[index] = combinedId; + validColors[index] = color; + index++; + } + zipFile.close(); } } -// + + public BaseBlock getNearestBlock(int color) { + long min = Long.MAX_VALUE; + int closest = 0; + int red1 = (color >> 16) & 0xFF; + int green1 = (color >> 8) & 0xFF; + int blue1 = (color >> 0) & 0xFF; + int alpha = (color >> 24) & 0xFF; + for (int i = 0; i < validColors.length; i++) { + int other = validColors[i]; + if (((other >> 24) & 0xFF) == alpha) { + long distance = colorDistance(red1, green1, blue1, other); + if (distance < min) { + min = distance; + closest = validBlockIds[i]; + } + } + } + return FaweCache.CACHE_BLOCK[closest]; + } + + public int getColor(BaseBlock block) { + return blockColors[block.getCombined()]; + } + + private boolean hasAlpha(int color) { + int alpha = (color >> 24) & 0xFF; + return alpha != 255; + } + + public long colorDistance(int c1, int c2) { + int red1 = (c1 >> 16) & 0xFF; + int green1 = (c1 >> 8) & 0xFF; + int blue1 = (c1 >> 0) & 0xFF; + return colorDistance(red1, green1, blue1, c2); + } + + private long colorDistance(int red1, int green1, int blue1, int c2) { + int red2 = (c2 >> 16) & 0xFF; + int green2 = (c2 >> 8) & 0xFF; + int blue2 = (c2 >> 0) & 0xFF; + int rmean = (red1 + red2) >> 1; + int r = red1 - red2; + int g = green1 - green2; + int b = blue1 - blue2; + return (((512 + rmean) * r * r) >> 8) + 4 * g * g + (((767 - rmean) * b * b) >> 8); + } + + public int getColor(BufferedImage image) { + int width = image.getWidth(); + int height = image.getHeight(); + long totalRed = 0; + long totalGreen = 0; + long totalBlue = 0; + long totalAlpha = 0; + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + int color = image.getRGB(x, y); + totalRed += (color >> 16) & 0xFF; + totalGreen += (color >> 8) & 0xFF; + totalBlue += (color >> 0) & 0xFF; + totalAlpha += (color >> 24) & 0xFF; + } + } + int a = width * height; + Color color = new Color((int) (totalRed / a), (int) (totalGreen / a), (int) (totalBlue / a), (int) (totalAlpha / a)); + return color.getRGB(); + } + // public Color getColor(BaseBlock block) { // long r; // long b; diff --git a/core/src/main/java/com/sk89q/worldedit/command/HistoryCommands.java b/core/src/main/java/com/sk89q/worldedit/command/HistoryCommands.java index b3e691fe..f412a5d9 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/HistoryCommands.java +++ b/core/src/main/java/com/sk89q/worldedit/command/HistoryCommands.java @@ -28,14 +28,12 @@ import com.boydti.fawe.database.RollbackDatabase; import com.boydti.fawe.logging.rollback.RollbackOptimizedHistory; import com.boydti.fawe.object.FawePlayer; import com.boydti.fawe.object.FaweQueue; -import com.boydti.fawe.object.MaskedFaweQueue; import com.boydti.fawe.object.RegionWrapper; import com.boydti.fawe.object.RunnableVal; import com.boydti.fawe.object.changeset.DiskStorageHistory; import com.boydti.fawe.regions.FaweMaskManager; import com.boydti.fawe.util.MainUtil; import com.boydti.fawe.util.MathMan; -import com.boydti.fawe.util.SetQueue; import com.sk89q.minecraft.util.commands.Command; import com.sk89q.minecraft.util.commands.CommandContext; import com.sk89q.minecraft.util.commands.CommandPermissions; @@ -180,15 +178,21 @@ public class HistoryCommands { final FaweQueue finalQueue; RegionWrapper[] allowedRegions = fp.getCurrentRegions(FaweMaskManager.MaskType.OWNER); - if (allowedRegions.length != 1 || !allowedRegions[0].isGlobal()) { - finalQueue = new MaskedFaweQueue(SetQueue.IMP.getNewQueue(fp.getWorld(), true, false), allowedRegions); - } else { - finalQueue = SetQueue.IMP.getNewQueue(fp.getWorld(), true, false); + if (allowedRegions == null) { + BBC.NO_REGION.send(fp); + return; } + // TODO mask the regions bot / top to the bottom and top coord in the allowedRegions + // TODO: then mask the edit to the bot / top +// if (allowedRegions.length != 1 || !allowedRegions[0].isGlobal()) { +// finalQueue = new MaskedFaweQueue(SetQueue.IMP.getNewQueue(fp.getWorld(), true, false), allowedRegions); +// } else { +// finalQueue = SetQueue.IMP.getNewQueue(fp.getWorld(), true, false); +// } database.getPotentialEdits(other, System.currentTimeMillis() - timeDiff, bot, top, new RunnableVal() { @Override public void run(DiskStorageHistory edit) { - edit.undo(fp); + edit.undo(fp, allowedRegions); BBC.ROLLBACK_ELEMENT.send(player, Fawe.imp().getWorldName(edit.getWorld()) + "/" + user + "-" + edit.getIndex()); count.incrementAndGet(); } diff --git a/core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java b/core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java index 66505630..3bf47b19 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java +++ b/core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java @@ -92,10 +92,16 @@ public class SchematicCommands { this.worldEdit = worldEdit; } - @Command(aliases = { "loadall" }, usage = "[] ", desc = "Load multiple clipboards (paste will randomly choose one)") + @Command( + aliases = { "loadall" }, + usage = "[] ", + help = "Load multiple clipboards\n" + + "The -r flag will apply random rotation", + desc = "Load multiple clipboards (paste will randomly choose one)" + ) @Deprecated @CommandPermissions({ "worldedit.clipboard.load", "worldedit.schematic.load", "worldedit.schematic.upload" }) - public void loadall(final Player player, final LocalSession session, @Optional("schematic") final String formatName, final String filename) throws FilenameException { + public void loadall(final Player player, final LocalSession session, @Optional("schematic") final String formatName, final String filename, @Switch('r') boolean randomRotate) throws FilenameException { final ClipboardFormat format = ClipboardFormat.findByAlias(formatName); if (format == null) { BBC.CLIPBOARD_INVALID_FORMAT.send(player, formatName); @@ -348,6 +354,7 @@ public class SchematicCommands { } for (int i = 0; i < len; i++) { switch (args.getString(i).toLowerCase()) { + case "me": case "mine": mine = true; break; diff --git a/core/src/main/java/com/sk89q/worldedit/extension/factory/HashTagPatternParser.java b/core/src/main/java/com/sk89q/worldedit/extension/factory/HashTagPatternParser.java index 28a9c6ec..fcb29637 100644 --- a/core/src/main/java/com/sk89q/worldedit/extension/factory/HashTagPatternParser.java +++ b/core/src/main/java/com/sk89q/worldedit/extension/factory/HashTagPatternParser.java @@ -1,5 +1,6 @@ package com.sk89q.worldedit.extension.factory; +import com.boydti.fawe.Fawe; import com.boydti.fawe.command.FaweParser; import com.boydti.fawe.command.SuggestInputParseException; import com.boydti.fawe.object.pattern.BiomePattern; @@ -30,6 +31,7 @@ import com.sk89q.worldedit.EmptyClipboardException; import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.blocks.BaseBlock; import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.extension.input.InputParseException; import com.sk89q.worldedit.extension.input.ParserContext; @@ -53,6 +55,7 @@ import com.sk89q.worldedit.world.registry.BundledBlockData; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import javafx.scene.paint.Color; public class HashTagPatternParser extends FaweParser { @@ -141,6 +144,16 @@ public class HashTagPatternParser extends FaweParser { throw new InputParseException("No session is available, so no clipboard is available"); } } + case "#color": { + if (split2.length > 1) { + Color color = Color.web(split2[1]); + java.awt.Color awtColor = new java.awt.Color((float) color.getRed(), (float) color.getGreen(), (float) color.getBlue(), (float) color.getOpacity()); + BaseBlock block = Fawe.get().getTextureUtil().getNearestBlock(awtColor.getRGB()); + return new BlockPattern(block); + } else { + throw new InputParseException("#color:"); + } + } case "#fullcopy": { LocalSession session = context.requireSession(); if (session != null) { @@ -148,12 +161,26 @@ public class HashTagPatternParser extends FaweParser { if (split2.length > 1) { String location = split2[1]; try { - ClipboardHolder[] clipboards = ClipboardFormat.SCHEMATIC.loadAllFromInput(context.getActor(), context.requireWorld().getWorldData(), location, true); + ClipboardHolder[] clipboards; + switch (location.toLowerCase()) { + case "#copy": + case "#clipboard": + ClipboardHolder clipboard = session.getExistingClipboard(); + if (clipboard == null) { + throw new InputParseException("To use #fullcopy, please first copy something to your clipboard"); + } + clipboards = new ClipboardHolder[] {clipboard}; + break; + default: + clipboards = ClipboardFormat.SCHEMATIC.loadAllFromInput(context.getActor(), context.requireWorld().getWorldData(), location, true); + break; + } if (clipboards == null) { throw new InputParseException("#fullcopy:"); } - boolean random = split2.length == 3 && split2[2].equalsIgnoreCase("true"); - return new RandomFullClipboardPattern(Request.request().getExtent(), context.requireWorld().getWorldData(), clipboards, random); + boolean randomRotate = split2.length >= 3 && split2[2].equalsIgnoreCase("true"); + boolean randomFlip = split2.length >= 4 && split2[3].equalsIgnoreCase("true"); + return new RandomFullClipboardPattern(Request.request().getExtent(), context.requireWorld().getWorldData(), clipboards, randomRotate, randomFlip); } catch (IOException e) { throw new RuntimeException(e); diff --git a/core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformManager.java b/core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformManager.java index 5ef9a893..e933a6bc 100644 --- a/core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformManager.java +++ b/core/src/main/java/com/sk89q/worldedit/extension/platform/PlatformManager.java @@ -509,7 +509,6 @@ public class PlatformManager { return; } } - break; } diff --git a/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/ClipboardFormat.java b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/ClipboardFormat.java index ed504c24..475a9e5d 100644 --- a/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/ClipboardFormat.java +++ b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/ClipboardFormat.java @@ -335,10 +335,16 @@ public enum ClipboardFormat { } File working = worldEdit.getWorkingDirectoryFile(config.saveDir); File dir = new File(working, (Settings.IMP.PATHS.PER_PLAYER_SCHEMATICS ? (player.getUniqueId().toString() + File.separator) : "") + input); + if (!dir.exists()) { + dir = new File(dir + "." + getExtension()); + } if (!dir.exists()) { if ((!input.contains("/") && !input.contains("\\")) || player.hasPermission("worldedit.schematic.load.other")) { dir = new File(worldEdit.getWorkingDirectoryFile(config.saveDir), input); } + if (!dir.exists()) { + dir = new File(dir + "." + getExtension()); + } } if (!dir.exists()) { if (message) BBC.SCHEMATIC_NOT_FOUND.send(player, input); diff --git a/core/src/main/java/com/sk89q/worldedit/math/convolution/HeightMap.java b/core/src/main/java/com/sk89q/worldedit/math/convolution/HeightMap.java index 188f1494..98dac20a 100644 --- a/core/src/main/java/com/sk89q/worldedit/math/convolution/HeightMap.java +++ b/core/src/main/java/com/sk89q/worldedit/math/convolution/HeightMap.java @@ -125,6 +125,16 @@ public class HeightMap { return layers ? applyLayers(newData) : apply(newData); } +// TODO +// public int averageFilter(int iterations) throws WorldEditException { +// Vector min = region.getMinimumPoint(); +// Vector max = region.getMaximumPoint(); +// int shift = layers ? 3 : 0; +// AverageHeightMapFilter filter = new AverageHeightMapFilter(data, width, height, min.getBlockY() << shift, max.getBlockY() << shift); +// int[] newData = filter.filter(iterations); +// return layers ? applyLayers(newData) : apply(newData); +// } + public int applyLayers(int[] data) throws WorldEditException { checkNotNull(data); 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 index 161dee01..58fb8917 100644 --- a/core/src/main/java/com/sk89q/worldedit/world/registry/BundledBlockData.java +++ b/core/src/main/java/com/sk89q/worldedit/world/registry/BundledBlockData.java @@ -19,6 +19,7 @@ package com.sk89q.worldedit.world.registry; +import com.boydti.fawe.FaweCache; import com.google.common.io.Resources; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -43,6 +44,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Logger; import javax.annotation.Nullable; @@ -62,9 +64,9 @@ 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 stateMap = new HashMap(); - private final Map localIdMap = new HashMap(); + private final Map idMap = new ConcurrentHashMap<>(); + public final Map stateMap = new ConcurrentHashMap<>(); + private final Map localIdMap = new ConcurrentHashMap<>(); private final BlockEntry[] legacyMap = new BlockEntry[4096]; @@ -102,6 +104,10 @@ public class BundledBlockData { return localIdMap.keySet(); } + public Set getStateNames() { + return stateMap.keySet(); + } + public List getBlockNames(String partial) { partial = partial.toLowerCase(); List blocks = new ArrayList<>(); @@ -142,19 +148,33 @@ public class BundledBlockData { return false; } idMap.put(entry.id, entry); - String id = (entry.id.contains(":") ? entry.id.split(":")[1] : entry.id).toLowerCase().replace(" ", "_"); - localIdMap.putIfAbsent(id, entry); + String modId, id; + if (entry.id.contains(":")) { + String[] split = entry.id.split(":"); + id = split[1]; + modId = split[0]; + } else { + modId = ""; + id = entry.id; + } idMap.putIfAbsent(id, entry); + localIdMap.putIfAbsent(id, entry); legacyMap[entry.legacyId] = entry; + stateMap.putIfAbsent(id, FaweCache.getBlock(entry.legacyId, 0)); + stateMap.putIfAbsent(entry.id, FaweCache.getBlock(entry.legacyId, 0)); if (entry.states == null) { return true; } for (Map.Entry stateEntry : entry.states.entrySet()) { for (Map.Entry valueEntry : stateEntry.getValue().valueMap().entrySet()) { String key = valueEntry.getKey(); - if (!stateMap.containsKey(key)) { - stateMap.put(key, new BaseBlock(entry.legacyId, valueEntry.getValue().data)); + if (key.equals("true")) { + key = stateEntry.getKey(); } + stateMap.putIfAbsent(id + ":" + key, FaweCache.getBlock(entry.legacyId, valueEntry.getValue().data)); + stateMap.putIfAbsent(entry.id + ":" + key, FaweCache.getBlock(entry.legacyId, valueEntry.getValue().data)); + stateMap.putIfAbsent(modId + ":" + key, FaweCache.getBlock(entry.legacyId, valueEntry.getValue().data)); + stateMap.putIfAbsent(key, FaweCache.getBlock(entry.legacyId, valueEntry.getValue().data)); } } FaweState half = entry.states.get("half"); @@ -275,11 +295,22 @@ public class BundledBlockData { @Nullable public Integer toLegacyId(String id) { BlockEntry entry = findById(id); - if (entry != null) { - return entry.legacyId; - } else { - return null; + if (entry == null) { + entry = localIdMap.get(id); + if (entry == null) { + int index = id.lastIndexOf('_'); + if (index == -1) { + return null; + } + String data = id.substring(index + 1, id.length()); + id = id.substring(0, index); + entry = localIdMap.get(id + ":" + data); + if (entry == null) { + return null; + } + } } + return entry.legacyId; } /** diff --git a/core/src/main/resources/extrablocks.json b/core/src/main/resources/extrablocks.json index 6b7dd988..dc85c82b 100644 --- a/core/src/main/resources/extrablocks.json +++ b/core/src/main/resources/extrablocks.json @@ -1,6 +1,9 @@ -// Add blocks here if you need block names and rotation to work -// See: https://github.com/sk89q/WorldEdit/blob/master/worldedit-core/src/main/resources/com/sk89q/worldedit/world/registry/blocks.json -// - WorldEdit only uses the id/names + rotation/facing properties. +// Add blocks here if you need block names, rotation and textures to work +// The following blocks are already bundled with FAWE: https://github.com/sk89q/WorldEdit/blob/master/worldedit-core/src/main/resources/com/sk89q/worldedit/world/registry/blocks.json +// To generating the blocks from forge mods see: +// - https://github.com/wizjany/ForgeUtils +// +// Help with ForgeUtils: wizjany @ http://webchat.esper.net/?nick=&channels=sk89q [ ] \ No newline at end of file