diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java index ee02dbc0..959111b3 100644 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java @@ -167,15 +167,21 @@ public class FaweBukkit implements IFawe, Listener { return null; } + @Override + public void registerPacketListener() { + PluginManager manager = Bukkit.getPluginManager(); + if (packetListener == null && manager.getPlugin("ProtocolLib") != null) { + packetListener = new CFIPacketListener(plugin); + } + } + @Override public synchronized ImageViewer getImageViewer(FawePlayer fp) { if (listeningImages && imageListener == null) return null; try { listeningImages = true; + registerPacketListener(); PluginManager manager = Bukkit.getPluginManager(); - if (manager.getPlugin("ProtocolLib") != null) { - packetListener = new CFIPacketListener(plugin); - } if (manager.getPlugin("PacketListenerApi") == null) { File output = new File(plugin.getDataFolder().getParentFile(), "PacketListenerAPI_v3.6.0-SNAPSHOT.jar"); diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/listener/CFIPacketListener.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/listener/CFIPacketListener.java index 2edbef6b..02db320a 100644 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/listener/CFIPacketListener.java +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/listener/CFIPacketListener.java @@ -2,11 +2,11 @@ package com.boydti.fawe.bukkit.listener; import com.boydti.fawe.FaweCache; import com.boydti.fawe.command.CFICommands; -import com.boydti.fawe.jnbt.anvil.HeightMapMCAGenerator; import com.boydti.fawe.object.FaweChunk; import com.boydti.fawe.object.FawePlayer; import com.boydti.fawe.object.FaweQueue; import com.boydti.fawe.object.RunnableVal3; +import com.boydti.fawe.object.brush.visualization.VirtualWorld; import com.boydti.fawe.util.SetQueue; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.ProtocolLibrary; @@ -42,7 +42,7 @@ import org.bukkit.inventory.PlayerInventory; import org.bukkit.plugin.Plugin; /** - * The CFIPacketListener handles packets for editing the HeightMapMCAGenerator + * The CFIPacketListener handles packets for editing the VirtualWorld * The generator is a virtual world which only the creator can see * - The virtual world is displayed inside the current world * - Block/Chunk/Movement packets need to be handled properly @@ -57,9 +57,9 @@ public class CFIPacketListener implements Listener { this.protocolmanager = ProtocolLibrary.getProtocolManager(); // Direct digging to the virtual world - registerBlockEvent(PacketType.Play.Client.BLOCK_DIG, false, new RunnableVal3() { + registerBlockEvent(PacketType.Play.Client.BLOCK_DIG, false, new RunnableVal3() { @Override - public void run(PacketEvent event, HeightMapMCAGenerator gen, Vector pt) { + public void run(PacketEvent event, VirtualWorld gen, Vector pt) { try { Player plr = event.getPlayer(); Vector realPos = pt.add(gen.getOrigin()); @@ -73,9 +73,9 @@ public class CFIPacketListener implements Listener { }); // Direct placing to the virtual world - RunnableVal3 placeTask = new RunnableVal3() { + RunnableVal3 placeTask = new RunnableVal3() { @Override - public void run(PacketEvent event, HeightMapMCAGenerator gen, Vector pt) { + public void run(PacketEvent event, VirtualWorld gen, Vector pt) { try { Player plr = event.getPlayer(); List hands = event.getPacket().getHands().getValues(); @@ -99,9 +99,9 @@ public class CFIPacketListener implements Listener { registerBlockEvent(PacketType.Play.Client.USE_ITEM, true, placeTask); // Cancel block change packets where the real world overlaps with the virtual one - registerBlockEvent(PacketType.Play.Server.BLOCK_CHANGE, false, new RunnableVal3() { + registerBlockEvent(PacketType.Play.Server.BLOCK_CHANGE, false, new RunnableVal3() { @Override - public void run(PacketEvent event, HeightMapMCAGenerator gen, Vector pt) { + public void run(PacketEvent event, VirtualWorld gen, Vector pt) { // Do nothing } }); @@ -112,7 +112,7 @@ public class CFIPacketListener implements Listener { public void onPacketSending(PacketEvent event) { if (!event.isServerPacket()) return; - HeightMapMCAGenerator gen = getGenerator(event); + VirtualWorld gen = getGenerator(event); if (gen != null) { Vector origin = gen.getOrigin(); PacketContainer packet = event.getPacket(); @@ -123,7 +123,7 @@ public class CFIPacketListener implements Listener { int ocx = origin.getBlockX() >> 4; int ocz = origin.getBlockZ() >> 4; - if (gen.contain(new Vector((cx - ocx) << 4, 0, (cz - ocz) << 4))) { + if (gen.contains(new Vector((cx - ocx) << 4, 0, (cz - ocz) << 4))) { event.setCancelled(true); Player plr = event.getPlayer(); @@ -147,7 +147,7 @@ public class CFIPacketListener implements Listener { Player player = event.getPlayer(); Location pos = player.getLocation(); - HeightMapMCAGenerator gen = getGenerator(event); + VirtualWorld gen = getGenerator(event); if (gen != null) { Vector origin = gen.getOrigin(); Vector pt = new Vector(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ()); @@ -158,7 +158,7 @@ public class CFIPacketListener implements Listener { int my = ints.read(2); int mz = ints.read(3); - if (gen.contain(pt.subtract(origin)) && mx == 0 && my == 0 && mz == 0) { + if (gen.contains(pt.subtract(origin)) && mx == 0 && my == 0 && mz == 0) { event.setCancelled(true); } } @@ -172,7 +172,7 @@ public class CFIPacketListener implements Listener { Player player = event.getPlayer(); Location pos = player.getLocation(); - HeightMapMCAGenerator gen = getGenerator(event); + VirtualWorld gen = getGenerator(event); if (gen != null) { Vector origin = gen.getOrigin(); Vector from = new Vector(pos.getBlockX(), pos.getBlockY(), pos.getBlockZ()); @@ -180,7 +180,7 @@ public class CFIPacketListener implements Listener { PacketContainer packet = event.getPacket(); StructureModifier doubles = packet.getDoubles(); Vector to = new Vector(doubles.read(0), doubles.read(1), doubles.read(2)); - if (gen.contain(to.subtract(origin)) && from.distanceSq(to) < 8) { + if (gen.contains(to.subtract(origin)) && from.distanceSq(to) < 8) { int id = packet.getIntegers().read(0); PacketContainer reply = new PacketContainer(PacketType.Play.Client.TELEPORT_ACCEPT); reply.getIntegers().write(0, id); @@ -202,14 +202,14 @@ public class CFIPacketListener implements Listener { public void onPacketSending(PacketEvent event) { if (!event.isServerPacket()) return; - HeightMapMCAGenerator gen = getGenerator(event); + VirtualWorld gen = getGenerator(event); if (gen != null) { PacketContainer packet = event.getPacket(); ChunkCoordIntPair chunk = packet.getChunkCoordIntPairs().read(0); Vector origin = gen.getOrigin(); int cx = chunk.getChunkX() - (origin.getBlockX() >> 4); int cz = chunk.getChunkZ() - (origin.getBlockX() >> 4); - if (gen.contain(new Vector(cx << 4, 0, cz << 4))) { + if (gen.contains(new Vector(cx << 4, 0, cz << 4))) { event.setCancelled(true); } } @@ -220,7 +220,7 @@ public class CFIPacketListener implements Listener { @EventHandler public void onTeleport(PlayerTeleportEvent event) { final Player player = event.getPlayer(); - HeightMapMCAGenerator gen = getGenerator(player); + VirtualWorld gen = getGenerator(player); if (gen != null) { Location from = event.getFrom(); Location to = event.getTo(); @@ -232,7 +232,7 @@ public class CFIPacketListener implements Listener { } } - private boolean sendBlockChange(Player plr, HeightMapMCAGenerator gen, Vector pt, Interaction action) { + private boolean sendBlockChange(Player plr, VirtualWorld gen, Vector pt, Interaction action) { PlatformManager platform = WorldEdit.getInstance().getPlatformManager(); com.sk89q.worldedit.entity.Player actor = FawePlayer.wrap(plr).getPlayer(); com.sk89q.worldedit.util.Location location = new com.sk89q.worldedit.util.Location(actor.getWorld(), pt); @@ -261,19 +261,22 @@ public class CFIPacketListener implements Listener { } } - private HeightMapMCAGenerator getGenerator(PacketEvent event) { + private VirtualWorld getGenerator(PacketEvent event) { return getGenerator(event.getPlayer()); } - private HeightMapMCAGenerator getGenerator(Player player) { - CFICommands.CFISettings settings = FawePlayer.wrap(player).getMeta("CFISettings"); + private VirtualWorld getGenerator(Player player) { + FawePlayer fp = FawePlayer.wrap(player); + VirtualWorld vw = fp.getSession().getVirtualWorld(); + if (vw != null) return vw; + CFICommands.CFISettings settings = fp.getMeta("CFISettings"); if (settings != null && settings.hasGenerator() && settings.getGenerator().hasPacketViewer()) { return settings.getGenerator(); } return null; } - private Vector getRelPos(PacketEvent event, HeightMapMCAGenerator generator) { + private Vector getRelPos(PacketEvent event, VirtualWorld generator) { PacketContainer packet = event.getPacket(); StructureModifier position = packet.getBlockPositionModifier(); BlockPosition loc = position.readSafely(0); @@ -283,13 +286,13 @@ public class CFIPacketListener implements Listener { return pt; } - private void handleBlockEvent(PacketEvent event, boolean relative, RunnableVal3 task) { - HeightMapMCAGenerator gen = getGenerator(event); + private void handleBlockEvent(PacketEvent event, boolean relative, RunnableVal3 task) { + VirtualWorld gen = getGenerator(event); if (gen != null) { Vector pt = getRelPos(event, gen); if (pt != null) { if (relative) pt = getRelative(event, pt); - if (gen.contain(pt)) { + if (gen.contains(pt)) { event.setCancelled(true); task.run(event, gen, pt); } @@ -297,7 +300,7 @@ public class CFIPacketListener implements Listener { } } - private void registerBlockEvent(PacketType type, boolean relative, RunnableVal3 task) { + private void registerBlockEvent(PacketType type, boolean relative, RunnableVal3 task) { protocolmanager.addPacketListener(new PacketAdapter(plugin, ListenerPriority.NORMAL, type) { @Override public void onPacketReceiving(final PacketEvent event) { diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_12/BukkitQueue_1_12.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_12/BukkitQueue_1_12.java index c35cdba4..a843d629 100644 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_12/BukkitQueue_1_12.java +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_12/BukkitQueue_1_12.java @@ -15,11 +15,7 @@ import com.boydti.fawe.object.RunnableVal; import com.boydti.fawe.object.brush.visualization.VisualChunk; import com.boydti.fawe.object.queue.LazyFaweChunk; import com.boydti.fawe.object.visitor.FaweChunkVisitor; -import com.boydti.fawe.util.MainUtil; -import com.boydti.fawe.util.MathMan; -import com.boydti.fawe.util.ReflectionUtils; -import com.boydti.fawe.util.SetQueue; -import com.boydti.fawe.util.TaskManager; +import com.boydti.fawe.util.*; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.ProtocolManager; @@ -36,49 +32,9 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; -import java.util.UUID; +import java.util.*; import java.util.concurrent.atomic.LongAdder; -import net.minecraft.server.v1_12_R1.BiomeBase; -import net.minecraft.server.v1_12_R1.BiomeCache; -import net.minecraft.server.v1_12_R1.Block; -import net.minecraft.server.v1_12_R1.BlockPosition; -import net.minecraft.server.v1_12_R1.ChunkProviderGenerate; -import net.minecraft.server.v1_12_R1.ChunkProviderServer; -import net.minecraft.server.v1_12_R1.ChunkSection; -import net.minecraft.server.v1_12_R1.DataPaletteBlock; -import net.minecraft.server.v1_12_R1.Entity; -import net.minecraft.server.v1_12_R1.EntityPlayer; -import net.minecraft.server.v1_12_R1.EntityTracker; -import net.minecraft.server.v1_12_R1.EntityTypes; -import net.minecraft.server.v1_12_R1.EnumDifficulty; -import net.minecraft.server.v1_12_R1.EnumGamemode; -import net.minecraft.server.v1_12_R1.EnumSkyBlock; -import net.minecraft.server.v1_12_R1.IBlockData; -import net.minecraft.server.v1_12_R1.IDataManager; -import net.minecraft.server.v1_12_R1.MinecraftServer; -import net.minecraft.server.v1_12_R1.NBTTagCompound; -import net.minecraft.server.v1_12_R1.NibbleArray; -import net.minecraft.server.v1_12_R1.PacketDataSerializer; -import net.minecraft.server.v1_12_R1.PacketPlayOutMapChunk; -import net.minecraft.server.v1_12_R1.PacketPlayOutMultiBlockChange; -import net.minecraft.server.v1_12_R1.PlayerChunk; -import net.minecraft.server.v1_12_R1.PlayerChunkMap; -import net.minecraft.server.v1_12_R1.RegionFile; -import net.minecraft.server.v1_12_R1.RegionFileCache; -import net.minecraft.server.v1_12_R1.ServerNBTManager; -import net.minecraft.server.v1_12_R1.TileEntity; -import net.minecraft.server.v1_12_R1.WorldChunkManager; -import net.minecraft.server.v1_12_R1.WorldData; -import net.minecraft.server.v1_12_R1.WorldManager; -import net.minecraft.server.v1_12_R1.WorldServer; -import net.minecraft.server.v1_12_R1.WorldSettings; -import net.minecraft.server.v1_12_R1.WorldType; +import net.minecraft.server.v1_12_R1.*; import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.World; @@ -583,6 +539,7 @@ public class BukkitQueue_1_12 extends BukkitQueue_0 6 || Settings.IMP.HISTORY.COMPRESSION_LEVEL > 6) { - Settings.IMP.CLIPBOARD.COMPRESSION_LEVEL = Math.min(6, Settings.IMP.CLIPBOARD.COMPRESSION_LEVEL); - Settings.IMP.HISTORY.COMPRESSION_LEVEL = Math.min(6, Settings.IMP.HISTORY.COMPRESSION_LEVEL); - debug("====== ZSTD COMPRESSION BINDING NOT FOUND ======"); + if (!Settings.IMP.EXPERIMENTAL.DISABLE_NATIVES) { + try { + com.github.luben.zstd.util.Native.load(); + } catch (Throwable e) { + if (Settings.IMP.CLIPBOARD.COMPRESSION_LEVEL > 6 || Settings.IMP.HISTORY.COMPRESSION_LEVEL > 6) { + Settings.IMP.CLIPBOARD.COMPRESSION_LEVEL = Math.min(6, Settings.IMP.CLIPBOARD.COMPRESSION_LEVEL); + Settings.IMP.HISTORY.COMPRESSION_LEVEL = Math.min(6, Settings.IMP.HISTORY.COMPRESSION_LEVEL); + debug("====== ZSTD COMPRESSION BINDING NOT FOUND ======"); + debug(e); + debug("==============================================="); + debug("FAWE will work but won't compress data as much"); + debug("==============================================="); + } + } + try { + net.jpountz.util.Native.load(); + } catch (Throwable e) { + debug("====== LZ4 COMPRESSION BINDING NOT FOUND ======"); debug(e); debug("==============================================="); - debug("FAWE will work but won't compress data as much"); + debug("FAWE will work but compression will be slower"); + debug(" - Try updating your JVM / OS"); + debug(" - Report this issue if you cannot resolve it"); debug("==============================================="); } } - try { - net.jpountz.util.Native.load(); - } catch (Throwable e) { - debug("====== LZ4 COMPRESSION BINDING NOT FOUND ======"); - debug(e); - debug("==============================================="); - debug("FAWE will work but compression will be slower"); - debug(" - Try updating your JVM / OS"); - debug(" - Report this issue if you cannot resolve it"); - debug("==============================================="); - } try { String arch = System.getenv("PROCESSOR_ARCHITECTURE"); String wow64Arch = System.getenv("PROCESSOR_ARCHITEW6432"); diff --git a/core/src/main/java/com/boydti/fawe/IFawe.java b/core/src/main/java/com/boydti/fawe/IFawe.java index 0c8b1e44..5708ee91 100644 --- a/core/src/main/java/com/boydti/fawe/IFawe.java +++ b/core/src/main/java/com/boydti/fawe/IFawe.java @@ -40,6 +40,8 @@ public interface IFawe { default ImageViewer getImageViewer(FawePlayer player) { return null; } + public default void registerPacketListener() {} + default int getPlayerCount() { return Fawe.get().getCachedPlayers().size(); } diff --git a/core/src/main/java/com/boydti/fawe/command/CFICommands.java b/core/src/main/java/com/boydti/fawe/command/CFICommands.java index 6c2e9ec7..1e01e139 100644 --- a/core/src/main/java/com/boydti/fawe/command/CFICommands.java +++ b/core/src/main/java/com/boydti/fawe/command/CFICommands.java @@ -8,12 +8,7 @@ import com.boydti.fawe.jnbt.anvil.HeightMapMCAGenerator; import com.boydti.fawe.object.FawePlayer; import com.boydti.fawe.object.RunnableVal; import com.boydti.fawe.object.clipboard.MultiClipboardHolder; -import com.boydti.fawe.util.CleanTextureUtil; -import com.boydti.fawe.util.FilteredTextureUtil; -import com.boydti.fawe.util.ImgurUtility; -import com.boydti.fawe.util.StringMan; -import com.boydti.fawe.util.TaskManager; -import com.boydti.fawe.util.TextureUtil; +import com.boydti.fawe.util.*; import com.boydti.fawe.util.chat.Message; import com.boydti.fawe.util.image.ImageUtil; import com.intellectualcrafters.plot.PS; @@ -33,11 +28,8 @@ import com.sk89q.minecraft.util.commands.Command; import com.sk89q.minecraft.util.commands.CommandContext; import com.sk89q.minecraft.util.commands.CommandException; import com.sk89q.minecraft.util.commands.CommandPermissions; -import com.sk89q.worldedit.EmptyClipboardException; -import com.sk89q.worldedit.LocalSession; +import com.sk89q.worldedit.*; import com.sk89q.worldedit.Vector; -import com.sk89q.worldedit.WorldEdit; -import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.blocks.BaseBlock; import com.sk89q.worldedit.command.MethodCommands; import com.sk89q.worldedit.entity.Player; @@ -63,10 +55,9 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.net.URL; -import java.util.ArrayDeque; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.*; import javax.imageio.ImageIO; @Command(aliases = {"/cfi"}, desc = "Create a world from images: [More Info](https://git.io/v5iDy)") @@ -95,7 +86,7 @@ public class CFICommands extends MethodCommands { ) @CommandPermissions("worldedit.anvil.cfi") public void heightmap(FawePlayer fp, BufferedImage image) { - HeightMapMCAGenerator generator = new HeightMapMCAGenerator(image, getFolder("CFI-" + UUID.randomUUID())); + HeightMapMCAGenerator generator = new HeightMapMCAGenerator(image, getFolder(generateName())); setup(generator, fp); } @@ -106,14 +97,20 @@ public class CFICommands extends MethodCommands { ) @CommandPermissions("worldedit.anvil.cfi") public void heightmap(FawePlayer fp, int width, int length) { - HeightMapMCAGenerator generator = new HeightMapMCAGenerator(width, length, getFolder("CFI-" + UUID.randomUUID())); + HeightMapMCAGenerator generator = new HeightMapMCAGenerator(width, length, getFolder(generateName())); setup(generator, fp); } + private String generateName() { + DateFormat df = new SimpleDateFormat("dd.MM.yyyy HH.mm.ss"); + String data = df.format(new Date()); + return data; + } + private void setup(HeightMapMCAGenerator generator, FawePlayer fp) { - CFISettings settings = getSettings(fp); - settings.remove().setGenerator(generator).bind(); + CFISettings settings = getSettings(fp).remove(); generator.setPacketViewer(fp); + settings.setGenerator(generator).bind(); generator.setImageViewer(Fawe.imp().getImageViewer(fp)); generator.update(); mainMenu(fp); @@ -1021,6 +1018,8 @@ public class CFICommands extends MethodCommands { protected String category; + private boolean bound; + public CFISettings(FawePlayer player) { this.fp = player; } @@ -1078,10 +1077,13 @@ public class CFICommands extends MethodCommands { public CFISettings setGenerator(HeightMapMCAGenerator generator) { this.generator = generator; + if (bound) fp.getSession().setVirtualWorld(generator); return this; } public CFISettings bind() { + if (generator != null) fp.getSession().setVirtualWorld(generator); + bound = true; fp.setMeta("CFISettings", this); return this; } @@ -1099,11 +1101,10 @@ public class CFICommands extends MethodCommands { fp.deleteMeta("CFISettings"); HeightMapMCAGenerator gen = this.generator; if (gen != null) { - gen.close(); - LocalSession session = fp.getSession(); - session.clearHistory(); + fp.getSession().setVirtualWorld(null); } popMessages(fp); + bound = false; generator = null; image = null; imageArg = null; diff --git a/core/src/main/java/com/boydti/fawe/command/Cancel.java b/core/src/main/java/com/boydti/fawe/command/Cancel.java index dba98c0e..492f0244 100644 --- a/core/src/main/java/com/boydti/fawe/command/Cancel.java +++ b/core/src/main/java/com/boydti/fawe/command/Cancel.java @@ -4,6 +4,7 @@ import com.boydti.fawe.config.BBC; import com.boydti.fawe.object.FaweCommand; import com.boydti.fawe.object.FawePlayer; import com.boydti.fawe.object.FaweQueue; +import com.boydti.fawe.object.brush.visualization.VirtualWorld; import com.boydti.fawe.util.SetQueue; import com.sk89q.worldedit.EditSession; import java.util.Collection; @@ -35,6 +36,10 @@ public class Cancel extends FaweCommand { } } } + VirtualWorld world = player.getSession().getVirtualWorld(); + if (world != null) { + world.clear(); + } BBC.WORLDEDIT_CANCEL_COUNT.send(player, cancelled); return true; } diff --git a/core/src/main/java/com/boydti/fawe/config/BBC.java b/core/src/main/java/com/boydti/fawe/config/BBC.java index 3ef2e24c..93a90f9c 100644 --- a/core/src/main/java/com/boydti/fawe/config/BBC.java +++ b/core/src/main/java/com/boydti/fawe/config/BBC.java @@ -185,7 +185,15 @@ public enum BBC { NOTHING_CONFIRMED("You have no actions pending confirmation.", "WorldEdit.Utility"), + SCHEMATIC_PROMPT_CLEAR("&7You may want to use &c%s0 &7to clear your current clipboard first", "Worldedit.Schematic"), + SCHEMATIC_SHOW("&7Displaying &a%s0&7 schematics from &a%s1&7:\n" + + "&8 - &aLeft click &7a structure to set your clipboard\n" + + "&8 - &aRight click &7to add a structure to your multi-clipboard\n" + + "&8 - &7Use &a%s2&7 to go back to the world", "Worldedit.Schematic"), SCHEMATIC_FORMAT("Available formats (Name: Lookup names)", "Worldedit.Schematic"), + SCHEMATIC_MOVE_EXISTS("&c%s0 already exists", "Worldedit.Schematic"), + SCHEMATIC_MOVE_SUCCESS("&a%s0 -> %s1", "Worldedit.Schematic"), + SCHEMATIC_MOVE_FAILED("&a%s0 no moved: %s1", "Worldedit.Schematic"), SCHEMATIC_LOADED("%s0 loaded. Paste it with //paste", "Worldedit.Schematic"), SCHEMATIC_SAVED("%s0 saved.", "Worldedit.Schematic"), SCHEMATIC_PAGE("Page must be %s", "WorldEdit.Schematic"), 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 dff4272e..3a333496 100644 --- a/core/src/main/java/com/boydti/fawe/config/Settings.java +++ b/core/src/main/java/com/boydti/fawe/config/Settings.java @@ -72,7 +72,10 @@ public class Settings extends Config { public String TEXTURES = "textures"; public String HEIGHTMAP = "heightmap"; public String HISTORY = "history"; - @Comment("Multiple servers can use the same clipboards") + @Comment({ + "Multiple servers can use the same clipboards", + " - Use a shared directory or NFS/Samba" + }) public String CLIPBOARD = "clipboard"; @Comment("Each player has their own sub directory for schematics") public boolean PER_PLAYER_SCHEMATICS = true; @@ -315,6 +318,11 @@ public class Settings extends Config { }) public boolean VANILLA_CUI = false; + + @Comment({ + "Disable using native libraries", + }) + public boolean DISABLE_NATIVES = false; } public static class WEB { diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/HeightMapMCAGenerator.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/HeightMapMCAGenerator.java index 15aad52e..7fd7049f 100644 --- a/core/src/main/java/com/boydti/fawe/jnbt/anvil/HeightMapMCAGenerator.java +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/HeightMapMCAGenerator.java @@ -3,41 +3,20 @@ package com.boydti.fawe.jnbt.anvil; import com.boydti.fawe.Fawe; import com.boydti.fawe.FaweCache; import com.boydti.fawe.example.SimpleCharFaweChunk; -import com.boydti.fawe.object.FaweChunk; -import com.boydti.fawe.object.FaweInputStream; -import com.boydti.fawe.object.FaweLocation; -import com.boydti.fawe.object.FaweOutputStream; -import com.boydti.fawe.object.FawePlayer; -import com.boydti.fawe.object.FaweQueue; -import com.boydti.fawe.object.Metadatable; -import com.boydti.fawe.object.PseudoRandom; -import com.boydti.fawe.object.RunnableVal2; +import com.boydti.fawe.object.*; +import com.boydti.fawe.object.brush.visualization.VirtualWorld; import com.boydti.fawe.object.change.StreamChange; import com.boydti.fawe.object.changeset.CFIChangeSet; -import com.boydti.fawe.object.collection.DifferentialArray; -import com.boydti.fawe.object.collection.DifferentialBlockBuffer; -import com.boydti.fawe.object.collection.IterableThreadLocal; -import com.boydti.fawe.object.collection.LocalBlockVector2DSet; -import com.boydti.fawe.object.collection.SummedAreaTable; +import com.boydti.fawe.object.collection.*; import com.boydti.fawe.object.exception.FaweException; import com.boydti.fawe.object.queue.LazyFaweChunk; import com.boydti.fawe.object.schematic.Schematic; -import com.boydti.fawe.util.CachedTextureUtil; -import com.boydti.fawe.util.RandomTextureUtil; -import com.boydti.fawe.util.ReflectionUtils; -import com.boydti.fawe.util.SetQueue; -import com.boydti.fawe.util.TaskManager; -import com.boydti.fawe.util.TextureUtil; +import com.boydti.fawe.util.*; import com.boydti.fawe.util.image.Drawable; import com.boydti.fawe.util.image.ImageViewer; import com.sk89q.jnbt.CompoundTag; -import com.sk89q.worldedit.BlockVector; -import com.sk89q.worldedit.EditSession; -import com.sk89q.worldedit.MaxChangedBlocksException; -import com.sk89q.worldedit.MutableBlockVector; +import com.sk89q.worldedit.*; import com.sk89q.worldedit.Vector; -import com.sk89q.worldedit.Vector2D; -import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.blocks.BaseBlock; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.blocks.BlockID; @@ -51,24 +30,18 @@ import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.session.ClipboardHolder; import com.sk89q.worldedit.util.TreeGenerator; -import com.sk89q.worldedit.world.SimpleWorld; import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.biome.BaseBiome; import com.sk89q.worldedit.world.registry.WorldData; import java.awt.image.BufferedImage; -import java.io.Closeable; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.lang.reflect.Field; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.UUID; +import java.util.*; import javax.annotation.Nullable; -public class HeightMapMCAGenerator extends MCAWriter implements SimpleWorld, FaweQueue, StreamChange, Closeable, Drawable { +public class HeightMapMCAGenerator extends MCAWriter implements StreamChange, Drawable, VirtualWorld { private final MutableBlockVector mutable = new MutableBlockVector(); private final ThreadLocal indexStore = new ThreadLocal() { @@ -251,6 +224,7 @@ public class HeightMapMCAGenerator extends MCAWriter implements SimpleWorld, Faw throw new UnsupportedOperationException("Not supported: Queue is not backed by a real world"); } + @Override public Vector getOrigin() { return new BlockVector(chunkOffset.getBlockX() << 4, 0, chunkOffset.getBlockZ() << 4); } @@ -286,6 +260,7 @@ public class HeightMapMCAGenerator extends MCAWriter implements SimpleWorld, Faw return viewer; } + @Override public void update() { if (viewer != null) { viewer.view(this); @@ -326,9 +301,6 @@ public class HeightMapMCAGenerator extends MCAWriter implements SimpleWorld, Faw e.printStackTrace(); } }); -// FaweChunk toSend = getSnapshot(chunk, finalCX, finalCZ); -// toSend.setLoc(HeightMapMCAGenerator.this, finalCX + OX, finalCZ + OZ); -// packetQueue.sendChunkUpdate(toSend, player); } } } @@ -695,6 +667,11 @@ public class HeightMapMCAGenerator extends MCAWriter implements SimpleWorld, Faw return new Vector(0, 0, 0); } + @Override + public FawePlayer getPlayer() { + return player; + } + @Override public Vector getMaximumPoint() { return new Vector(getWidth() - 1, 255, getLength() - 1); @@ -774,6 +751,7 @@ public class HeightMapMCAGenerator extends MCAWriter implements SimpleWorld, Faw return new SimpleCharFaweChunk(this, chunkX, chunkZ); } + @Override public FaweChunk getSnapshot(int chunkX, int chunkZ) { return getSnapshot(null, chunkX, chunkZ); } @@ -899,9 +877,9 @@ public class HeightMapMCAGenerator extends MCAWriter implements SimpleWorld, Faw } @Override - public void close() { + public void close(boolean update) { clear(); - if (chunkOffset != null && player != null) { + if (chunkOffset != null && player != null && update) { FaweQueue packetQueue = SetQueue.IMP.getNewQueue(player.getWorld(), true, false); int lenCX = (getWidth() + 15) >> 4; @@ -925,6 +903,11 @@ public class HeightMapMCAGenerator extends MCAWriter implements SimpleWorld, Faw } } } + if (player != null) { + player.deleteMeta("CFISettings"); + LocalSession session = player.getSession(); + session.clearHistory(); + } player = null; chunkOffset = null; } @@ -2151,7 +2134,7 @@ public class HeightMapMCAGenerator extends MCAWriter implements SimpleWorld, Faw @Override public int getMaxY() { - return SimpleWorld.super.getMaxY(); + return 255; } @Override diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAChunk.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAChunk.java index 31f7e47e..34be77fc 100644 --- a/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAChunk.java +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAChunk.java @@ -11,24 +11,11 @@ import com.boydti.fawe.util.ArrayUtil; import com.boydti.fawe.util.MainUtil; import com.boydti.fawe.util.MathMan; import com.boydti.fawe.util.ReflectionUtils; -import com.sk89q.jnbt.CompoundTag; -import com.sk89q.jnbt.IntTag; -import com.sk89q.jnbt.ListTag; -import com.sk89q.jnbt.NBTConstants; -import com.sk89q.jnbt.NBTInputStream; -import com.sk89q.jnbt.NBTOutputStream; -import com.sk89q.jnbt.Tag; +import com.sk89q.jnbt.*; import java.io.DataOutput; import java.io.DataOutputStream; import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; -import java.util.UUID; +import java.util.*; public class MCAChunk extends FaweChunk { @@ -102,13 +89,7 @@ public class MCAChunk extends FaweChunk { } } - public byte[] toBytes(byte[] buffer) throws IOException { - if (buffer == null) { - buffer = new byte[8192]; - } - FastByteArrayOutputStream buffered = new FastByteArrayOutputStream(buffer); - DataOutputStream dataOut = new DataOutputStream(buffered); - NBTOutputStream nbtOut = new NBTOutputStream((DataOutput) dataOut); + public void write(NBTOutputStream nbtOut) throws IOException { nbtOut.writeNamedTagName("", NBTConstants.TYPE_COMPOUND); nbtOut.writeLazyCompoundTag("Level", new NBTOutputStream.LazyWrite() { @Override @@ -135,12 +116,12 @@ public class MCAChunk extends FaweChunk { } out.writeNamedTag("HeightMap", heightMap); out.writeNamedTagName("Sections", NBTConstants.TYPE_LIST); - dataOut.writeByte(NBTConstants.TYPE_COMPOUND); + nbtOut.getOutputStream().writeByte(NBTConstants.TYPE_COMPOUND); int len = 0; for (int layer = 0; layer < ids.length; layer++) { if (ids[layer] != null) len++; } - dataOut.writeInt(len); + nbtOut.getOutputStream().writeInt(len); for (int layer = 0; layer < ids.length; layer++) { byte[] idLayer = ids[layer]; if (idLayer == null) { @@ -156,7 +137,17 @@ public class MCAChunk extends FaweChunk { } }); nbtOut.writeEndTag(); - nbtOut.close(); + } + + public byte[] toBytes(byte[] buffer) throws IOException { + if (buffer == null) { + buffer = new byte[8192]; + } + FastByteArrayOutputStream buffered = new FastByteArrayOutputStream(buffer); + DataOutputStream dataOut = new DataOutputStream(buffered); + try (NBTOutputStream nbtOut = new NBTOutputStream((DataOutput) dataOut)) { + write(nbtOut); + } return buffered.toByteArray(); } @@ -465,7 +456,7 @@ public class MCAChunk extends FaweChunk { return FaweCache.asTag(root); } - public MCAChunk(NBTInputStream nis, FaweQueue parent, int x, int z, int compressedSize) throws IOException { + public MCAChunk(NBTInputStream nis, FaweQueue parent, int x, int z, boolean readPos) throws IOException { super(parent, x, z); ids = new byte[16][]; data = new byte[16][]; @@ -527,6 +518,20 @@ public class MCAChunk extends FaweChunk { heightMap = value; } }); + if (readPos) { + streamer.addReader(".Level.xPos", new RunnableVal2() { + @Override + public void run(Integer index, Integer value) { + MCAChunk.this.setLoc(getParent(), value, getZ()); + } + }); + streamer.addReader(".Level.zPos", new RunnableVal2() { + @Override + public void run(Integer index, Integer value) { + MCAChunk.this.setLoc(getParent(), getX(), value); + } + }); + } streamer.readFully(); } diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAFile.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAFile.java index 4b5ddbaa..b274b241 100644 --- a/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAFile.java +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAFile.java @@ -86,6 +86,17 @@ public class MCAFile { Z = Integer.parseInt(split[2]); } + public MCAFile(FaweQueue parent, int mcrX, int mcrZ) throws Exception { + this(parent, mcrX, mcrZ, new File(parent.getSaveFolder(), "r." + mcrX + "." + mcrZ + ".mca")); + } + + public MCAFile(FaweQueue parent, int mcrX, int mcrZ, File file) { + this.queue = parent; + this.file = file; + X = mcrX; + Z = mcrZ; + } + public void clear() { if (raf != null) { try { @@ -130,12 +141,14 @@ public class MCAFile { try { if (raf == null) { this.locations = new byte[4096]; - this.raf = new RandomAccessFile(file, "rw"); - if (raf.length() < 8192) { - raf.setLength(8192); - } else { - raf.seek(0); - raf.readFully(locations); + if (file != null) { + this.raf = new RandomAccessFile(file, "rw"); + if (raf.length() < 8192) { + raf.setLength(8192); + } else { + raf.seek(0); + raf.readFully(locations); + } } } } catch (Throwable e) { @@ -143,10 +156,6 @@ public class MCAFile { } } - public MCAFile(FaweQueue parent, int mcrX, int mcrZ) throws Exception { - this(parent, new File(parent.getSaveFolder(), "r." + mcrX + "." + mcrZ + ".mca")); - } - public int getX() { return X; } @@ -196,7 +205,7 @@ public class MCAFile { return null; } NBTInputStream nis = getChunkIS(offset); - MCAChunk chunk = new MCAChunk(nis, queue, cx, cz, size); + MCAChunk chunk = new MCAChunk(nis, queue, cx, cz, false); nis.close(); int pair = MathMan.pair((short) (cx & 31), (short) (cz & 31)); synchronized (chunks) { diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAQueue.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAQueue.java index 6e6d365a..d7d2d039 100644 --- a/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAQueue.java +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAQueue.java @@ -116,38 +116,38 @@ public class MCAQueue extends NMSMappedFaweQueue> 4; int otherTCX = (otx) >> 4; int otherTCZ = (otz) >> 4; - int cx = newChunk.getX(); - int cz = newChunk.getZ(); - int cbx = (cx << 4) - oX; - int cbz = (cz << 4) - oZ; + int cx = newChunk.getX(); + int cz = newChunk.getZ(); + int cbx = (cx << 4) - oX; + int cbz = (cz << 4) - oZ; - boolean changed = false; + boolean changed = false; for (int otherCZ = otherBCZ; otherCZ <= otherTCZ; otherCZ++) { - for (int otherCX = otherBCX; otherCX <= otherTCX; otherCX++) { - FaweChunk chunk; - synchronized (this) { - chunk = this.getFaweChunk(otherCX, otherCZ); - } - if (!(chunk instanceof NullFaweChunk)) { - changed = true; - MCAChunk other = (MCAChunk) chunk; - int ocbx = otherCX << 4; - int ocbz = otherCZ << 4; - int octx = ocbx + 15; - int octz = ocbz + 15; - int offsetY = 0; - int minX = obx > ocbx ? (obx - ocbx) & 15 : 0; - int maxX = otx < octx ? (otx - ocbx) : 15; - int minZ = obz > ocbz ? (obz - ocbz) & 15 : 0; - int maxZ = otz < octz ? (otz - ocbz) : 15; - int offsetX = ocbx - cbx; - int offsetZ = ocbz - cbz; - newChunk.copyFrom(other, minX, maxX, 0, 255, minZ, maxZ, offsetX, offsetY, offsetZ); - } + for (int otherCX = otherBCX; otherCX <= otherTCX; otherCX++) { + FaweChunk chunk; + synchronized (this) { + chunk = this.getFaweChunk(otherCX, otherCZ); + } + if (!(chunk instanceof NullFaweChunk)) { + changed = true; + MCAChunk other = (MCAChunk) chunk; + int ocbx = otherCX << 4; + int ocbz = otherCZ << 4; + int octx = ocbx + 15; + int octz = ocbz + 15; + int offsetY = 0; + int minX = obx > ocbx ? (obx - ocbx) & 15 : 0; + int maxX = otx < octx ? (otx - ocbx) : 15; + int minZ = obz > ocbz ? (obz - ocbz) & 15 : 0; + int maxZ = otz < octz ? (otz - ocbz) : 15; + int offsetX = ocbx - cbx; + int offsetZ = ocbz - cbz; + newChunk.copyFrom(other, minX, maxX, 0, 255, minZ, maxZ, offsetX, offsetY, offsetZ); } } - return changed; } + return changed; +} @Override public boolean setMCA(int mcaX, int mcaZ, RegionWrapper region, Runnable whileLocked, boolean save, boolean unload) { diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAQueueMap.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAQueueMap.java index aa714bf5..e2dab560 100644 --- a/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAQueueMap.java +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAQueueMap.java @@ -53,13 +53,19 @@ public class MCAQueueMap implements IFaweQueueMap { if (lastFile == null) { try { queue.setMCA(lastFileX, lastFileZ, RegionWrapper.GLOBAL(), null, true, false); - File file = new File(queue.getSaveFolder(), "r." + lastFileX + "." + lastFileZ + ".mca"); - if (create) { - File parent = file.getParentFile(); - if (!parent.exists()) parent.mkdirs(); - if (!file.exists()) file.createNewFile(); + File save = queue.getSaveFolder(); + File file; + if (save != null) { + file = new File(queue.getSaveFolder(), "r." + lastFileX + "." + lastFileZ + ".mca"); + if (create) { + File parent = file.getParentFile(); + if (!parent.exists()) parent.mkdirs(); + if (!file.exists()) file.createNewFile(); + } + } else { + file = null; } - lastFile = tmp = new MCAFile(queue, file); + lastFile = tmp = new MCAFile(queue, mcaX, mcaZ, file); } catch (FaweException.FaweChunkLoadException ignore) { lastFile = null; return null; diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/DebugFixAir.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/DebugFixAir.java index eb20e77a..2f018bd3 100644 --- a/core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/DebugFixAir.java +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/DebugFixAir.java @@ -56,7 +56,7 @@ public class DebugFixAir extends MCAFilterCounter { } } ids0[i] = BlockID.BEDROCK; - chunk.ids[4][i] = BlockID.GRASS; + if (chunk.ids[4][i] == 0) chunk.ids[4][i] = BlockID.GRASS; cache.add(256); } } diff --git a/core/src/main/java/com/boydti/fawe/object/DelegateConsumer.java b/core/src/main/java/com/boydti/fawe/object/DelegateConsumer.java new file mode 100644 index 00000000..1414df18 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/DelegateConsumer.java @@ -0,0 +1,16 @@ +package com.boydti.fawe.object; + +import java.util.function.Consumer; + +public abstract class DelegateConsumer implements Consumer { + private final Consumer parent; + + public DelegateConsumer(Consumer parent) { + this.parent = parent; + } + + @Override + public void accept(T o) { + parent.accept(o); + } +} diff --git a/core/src/main/java/com/boydti/fawe/object/FaweInputStream.java b/core/src/main/java/com/boydti/fawe/object/FaweInputStream.java index 07ad034b..bee1c7bc 100644 --- a/core/src/main/java/com/boydti/fawe/object/FaweInputStream.java +++ b/core/src/main/java/com/boydti/fawe/object/FaweInputStream.java @@ -1,5 +1,6 @@ package com.boydti.fawe.object; +import com.boydti.fawe.util.IOUtil; import com.sk89q.jnbt.NBTInputStream; import com.sk89q.jnbt.NamedTag; import java.io.DataInputStream; @@ -64,16 +65,8 @@ public class FaweInputStream extends DataInputStream { } } - public int readVarInt() throws IOException { - int i = 0; - int offset = 0; - int b; - while ((b = read()) > 127) { - i |= (b - 128) << offset; - offset += 7; - } - i |= b << offset; - return i; + public final int readVarInt() throws IOException { + return IOUtil.readVarInt(this); } @Override diff --git a/core/src/main/java/com/boydti/fawe/object/FaweLimit.java b/core/src/main/java/com/boydti/fawe/object/FaweLimit.java index 25964fab..389c681b 100644 --- a/core/src/main/java/com/boydti/fawe/object/FaweLimit.java +++ b/core/src/main/java/com/boydti/fawe/object/FaweLimit.java @@ -18,7 +18,6 @@ public class FaweLimit { public boolean FAST_PLACEMENT = false; public boolean CONFIRM_LARGE = true; - public static FaweLimit MAX; static { diff --git a/core/src/main/java/com/boydti/fawe/object/FawePlayer.java b/core/src/main/java/com/boydti/fawe/object/FawePlayer.java index 2e834cdc..1cef0caf 100644 --- a/core/src/main/java/com/boydti/fawe/object/FawePlayer.java +++ b/core/src/main/java/com/boydti/fawe/object/FawePlayer.java @@ -5,7 +5,7 @@ import com.boydti.fawe.FaweAPI; import com.boydti.fawe.command.CFICommands; import com.boydti.fawe.config.BBC; import com.boydti.fawe.config.Settings; -import com.boydti.fawe.jnbt.anvil.HeightMapMCAGenerator; +import com.boydti.fawe.object.brush.visualization.VirtualWorld; import com.boydti.fawe.object.clipboard.DiskOptimizedClipboard; import com.boydti.fawe.object.exception.FaweException; import com.boydti.fawe.regions.FaweMaskManager; @@ -302,6 +302,13 @@ public abstract class FawePlayer extends Metadatable { return false; } + public boolean checkAction() { + long time = getMeta("faweActionTick", Long.MIN_VALUE); + long tick = Fawe.get().getTimer().getTick(); + setMeta("faweActionTick", tick); + return tick > time; + } + /** * Loads any history items from disk: * - Should already be called if history on disk is enabled @@ -610,16 +617,24 @@ public abstract class FawePlayer extends Metadatable { return new EditSessionBuilder(getWorld()).player(this).build(); } + public void setVirtualWorld(VirtualWorld world) { + getSession().setVirtualWorld(world); + } + /** * Get the World the player is editing in (may not match the world they are in)
* - e.g. If they are editing a CFI world.
* @return Editing world */ public World getWorldForEditing() { - CFICommands.CFISettings cfi = getMeta("CFISettings"); - if (cfi != null && cfi.hasGenerator() && cfi.getGenerator().hasPacketViewer()) { - return cfi.getGenerator(); + VirtualWorld virtual = getSession().getVirtualWorld(); + if (virtual != null) { + return virtual; } +// CFICommands.CFISettings cfi = getMeta("CFISettings"); +// if (cfi != null && cfi.hasGenerator() && cfi.getGenerator().hasPacketViewer()) { +// return cfi.getGenerator(); +// } return WorldEdit.getInstance().getPlatformManager().getWorldForEditing(getWorld()); } @@ -640,8 +655,8 @@ public abstract class FawePlayer extends Metadatable { } PlayerProxy proxy = new PlayerProxy(player, permActor, cuiActor, world); - if (world instanceof HeightMapMCAGenerator) { - proxy.setOffset(Vector.ZERO.subtract(((HeightMapMCAGenerator) world).getOrigin())); + if (world instanceof VirtualWorld) { + proxy.setOffset(Vector.ZERO.subtract(((VirtualWorld) world).getOrigin())); } return proxy; } diff --git a/core/src/main/java/com/boydti/fawe/object/RegionWrapper.java b/core/src/main/java/com/boydti/fawe/object/RegionWrapper.java index cb4d3533..ed60c8fc 100644 --- a/core/src/main/java/com/boydti/fawe/object/RegionWrapper.java +++ b/core/src/main/java/com/boydti/fawe/object/RegionWrapper.java @@ -3,6 +3,7 @@ package com.boydti.fawe.object; import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.regions.CuboidRegion; +@Deprecated public class RegionWrapper extends CuboidRegion { private final static RegionWrapper GLOBAL = new RegionWrapper(Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE); diff --git a/core/src/main/java/com/boydti/fawe/object/brush/visualization/ImmutableVirtualWorld.java b/core/src/main/java/com/boydti/fawe/object/brush/visualization/ImmutableVirtualWorld.java new file mode 100644 index 00000000..64bc3cb2 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/brush/visualization/ImmutableVirtualWorld.java @@ -0,0 +1,265 @@ +package com.boydti.fawe.object.brush.visualization; + +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.example.SimpleCharFaweChunk; +import com.boydti.fawe.object.FaweChunk; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.object.RunnableVal2; +import com.boydti.fawe.object.exception.FaweException; +import com.boydti.fawe.util.SetQueue; +import com.sk89q.jnbt.CompoundTag; +import com.sk89q.worldedit.*; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.blocks.BaseItemStack; +import com.sk89q.worldedit.function.operation.Operation; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.util.TreeGenerator; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldedit.world.biome.BaseBiome; +import java.io.File; +import java.util.Collection; +import java.util.Collections; +import java.util.UUID; +import javax.annotation.Nullable; + +public abstract class ImmutableVirtualWorld implements VirtualWorld { + @Override + public int getMaxY() { + return 255; + } + + @Override + public Collection getFaweChunks() { + return Collections.emptyList(); + } + + @Override + public boolean regenerateChunk(int x, int z, @Nullable BaseBiome biome, @Nullable Long seed) { + return unsupported(); + } + + @Override + public void sendBlockUpdate(FaweChunk chunk, FawePlayer... players) { + + } + + @Override + public File getSaveFolder() { + return null; + } + + @Override + public void addNotifyTask(int x, int z, Runnable runnable) { + if (runnable != null) runnable.run(); + } + + @Override + public BaseBiome getBiome(Vector2D position) { + return FaweCache.getBiome(0); + } + + @Override + public int getCombinedId4Data(int x, int y, int z, int def) { + return getCombinedId4Data(x, y, z); + } + + @Override + public int getCachedCombinedId4Data(int x, int y, int z) throws FaweException.FaweChunkLoadException { + return getCombinedId4Data(x, y, z); + } + + @Override + public boolean hasSky() { + return true; + } + + @Override + public int getSkyLight(int x, int y, int z) { + return 15; + } + + @Override + public int getEmmittedLight(int x, int y, int z) { + return 0; + } + + @Override + public CompoundTag getTileEntity(int x, int y, int z) throws FaweException.FaweChunkLoadException { + return null; + } + + @Override + public int size() { + return 0; + } + + @Override + public void setWorld(String world) { + + } + + @Override + public World getWEWorld() { + return this; + } + + @Override + public String getWorldName() { + return getName(); + } + + @Override + public long getModified() { + return 0; + } + + @Override + public void setModified(long modified) { + // Unsupported + } + + @Override + public RunnableVal2 getProgressTask() { + return null; + } + + @Override + public void setProgressTask(RunnableVal2 progressTask) { + + } + + @Override + public void setChangeTask(RunnableVal2 changeTask) { + + } + + @Override + public RunnableVal2 getChangeTask() { + return null; + } + + @Override + public SetQueue.QueueStage getStage() { + return SetQueue.QueueStage.NONE; + } + + @Override + public void setStage(SetQueue.QueueStage stage) { + // Not supported + } + + @Override + public void addNotifyTask(Runnable runnable) { + runnable.run(); + } + + @Override + public void runTasks() { + + } + + @Override + public void addTask(Runnable whenFree) { + whenFree.run(); + } + + @Override + public boolean isEmpty() { + return true; + } + + @Nullable + @Override + public Operation commit() { + return null; + } + + @Override + public String getName() { + return Integer.toString(hashCode()); + } + + @Override + public boolean setBlock(Vector position, BaseBlock block, boolean notifyAndLight) throws WorldEditException { + return setBlock(position, block); + } + + @Override + public int getBlockLightLevel(Vector position) { + return 0; + } + + @Override + public boolean clearContainerBlockContents(Vector position) { + return unsupported(); + } + + @Override + public void dropItem(Vector position, BaseItemStack item) { + unsupported(); + } + + @Override + public boolean generateTree(TreeGenerator.TreeType type, EditSession editSession, Vector position) throws MaxChangedBlocksException { + return unsupported(); + } + + @Override + public FaweChunk getFaweChunk(int chunkX, int chunkZ) { + return new SimpleCharFaweChunk(this, chunkX, chunkZ); + } + + @Override + public void setEntity(int x, int y, int z, CompoundTag tag) { + unsupported(); + } + + @Override + public boolean setBlock(Vector pt, BaseBlock block) throws WorldEditException { + return unsupported(); + } + + @Override + public boolean setBlock(int x, int y, int z, int id, int data) { + return unsupported(); + } + + @Override + public void setTile(int x, int y, int z, CompoundTag tag) { + unsupported(); + } + + @Override + public void removeEntity(int x, int y, int z, UUID uuid) { + unsupported(); + } + + @Override + public boolean setBiome(int x, int z, BaseBiome biome) { + return unsupported(); + } + + @Override + public void setChunk(FaweChunk chunk) { + unsupported(); + } + + @Override + public boolean next(int amount, long time) { + return unsupported(); + } + + @Override + public boolean regenerate(Region region, EditSession editSession) { + return unsupported(); + } + + @Override + public void clear() { + // do nothing - world is immutable + } + + private boolean unsupported() { + throw new UnsupportedOperationException("World is immutable"); + } +} diff --git a/core/src/main/java/com/boydti/fawe/object/brush/visualization/VirtualWorld.java b/core/src/main/java/com/boydti/fawe/object/brush/visualization/VirtualWorld.java new file mode 100644 index 00000000..0f87a0e9 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/brush/visualization/VirtualWorld.java @@ -0,0 +1,51 @@ +package com.boydti.fawe.object.brush.visualization; + +import com.boydti.fawe.object.FaweChunk; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.object.FaweQueue; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.event.platform.BlockInteractEvent; +import com.sk89q.worldedit.event.platform.PlayerInputEvent; +import com.sk89q.worldedit.world.SimpleWorld; +import java.io.Closeable; +import java.io.IOException; + +public interface VirtualWorld extends SimpleWorld, FaweQueue, Closeable { + Vector getOrigin(); + + FaweChunk getSnapshot(int chunkX, int chunkZ); + + @Override + int getMaxY(); + + @Override + boolean setBlock(Vector pt, BaseBlock block) throws WorldEditException; + + @Override + default Vector getMaximumPoint() { + return FaweQueue.super.getMaximumPoint(); + } + + @Override + default Vector getMinimumPoint() { + return FaweQueue.super.getMinimumPoint(); + } + + FawePlayer getPlayer(); + + void update(); + + @Override + default void close() throws IOException { + close(true); + } + + void close(boolean update) throws IOException; + + default void handleBlockInteract(Player player, Vector pos, BlockInteractEvent event) {} + + default void handlePlayerInput(Player player, PlayerInputEvent event) {} +} diff --git a/core/src/main/java/com/boydti/fawe/object/changeset/CFIChangeSet.java b/core/src/main/java/com/boydti/fawe/object/changeset/CFIChangeSet.java index a6793f34..de448e6e 100644 --- a/core/src/main/java/com/boydti/fawe/object/changeset/CFIChangeSet.java +++ b/core/src/main/java/com/boydti/fawe/object/changeset/CFIChangeSet.java @@ -20,7 +20,7 @@ public class CFIChangeSet extends FaweChangeSet { public CFIChangeSet(HeightMapMCAGenerator hmmg, UUID uuid) throws IOException { super(hmmg); - File folder = MainUtil.getFile(Fawe.imp().getDirectory(), Settings.IMP.PATHS.HISTORY + File.separator + uuid + File.separator + hmmg.getWorldName()); + File folder = MainUtil.getFile(Fawe.imp().getDirectory(), Settings.IMP.PATHS.HISTORY + File.separator + uuid + File.separator + "CFI" + File.separator + hmmg.getWorldName()); int max = MainUtil.getMaxFileId(folder); this.file = new File(folder, Integer.toString(max) + ".cfi"); File parent = this.file.getParentFile(); 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 857c4022..68c8a0d0 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 @@ -6,9 +6,7 @@ import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.session.ClipboardHolder; import com.sk89q.worldedit.world.registry.WorldData; import java.net.URI; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; +import java.util.*; import static com.google.common.base.Preconditions.checkNotNull; @@ -139,6 +137,18 @@ public class MultiClipboardHolder extends URIClipboardHolder { return available[index]; } + @Override + public Set getURIs() { + Set set = new HashSet<>(); + for (ClipboardHolder holder : getHolders()) { + if (holder instanceof URIClipboardHolder) { + URI uri = ((URIClipboardHolder) holder).getUri(); + if (!uri.toString().isEmpty()) set.add(uri); + } + } + return set; + } + @Override public void close() { cached = null; diff --git a/core/src/main/java/com/boydti/fawe/object/clipboard/URIClipboardHolder.java b/core/src/main/java/com/boydti/fawe/object/clipboard/URIClipboardHolder.java index de1db2c5..108ce264 100644 --- a/core/src/main/java/com/boydti/fawe/object/clipboard/URIClipboardHolder.java +++ b/core/src/main/java/com/boydti/fawe/object/clipboard/URIClipboardHolder.java @@ -4,6 +4,8 @@ import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.session.ClipboardHolder; import com.sk89q.worldedit.world.registry.WorldData; import java.net.URI; +import java.util.Collections; +import java.util.Set; import static com.google.common.base.Preconditions.checkNotNull; @@ -30,4 +32,8 @@ public class URIClipboardHolder extends ClipboardHolder { public URI getUri() { return uri; } + + public Set getURIs() { + return Collections.singleton(uri); + } } diff --git a/core/src/main/java/com/boydti/fawe/object/schematic/visualizer/SchemVis.java b/core/src/main/java/com/boydti/fawe/object/schematic/visualizer/SchemVis.java new file mode 100644 index 00000000..75fabb28 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/schematic/visualizer/SchemVis.java @@ -0,0 +1,553 @@ +package com.boydti.fawe.object.schematic.visualizer; + +import com.boydti.fawe.config.BBC; +import com.boydti.fawe.jnbt.anvil.MCAChunk; +import com.boydti.fawe.jnbt.anvil.MCAQueue; +import com.boydti.fawe.object.*; +import com.boydti.fawe.object.brush.visualization.ImmutableVirtualWorld; +import com.boydti.fawe.object.clipboard.LazyClipboardHolder; +import com.boydti.fawe.object.clipboard.MultiClipboardHolder; +import com.boydti.fawe.object.clipboard.URIClipboardHolder; +import com.boydti.fawe.object.exception.FaweException; +import com.boydti.fawe.object.queue.LazyFaweChunk; +import com.boydti.fawe.object.schematic.Schematic; +import com.boydti.fawe.util.*; +import com.google.common.io.ByteSource; +import com.google.common.io.Files; +import com.sk89q.jnbt.NBTInputStream; +import com.sk89q.jnbt.NBTOutputStream; +import com.sk89q.worldedit.*; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.blocks.BlockID; +import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.event.platform.PlayerInputEvent; +import com.sk89q.worldedit.extent.clipboard.Clipboard; +import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat; +import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader; +import com.sk89q.worldedit.session.ClipboardHolder; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.util.TargetBlock; +import com.sk89q.worldedit.world.registry.WorldData; +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectIterator; +import java.io.*; +import java.net.URI; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.util.*; + +/** + * An Immutable virtual world used to display & select schematics + */ +public class SchemVis extends ImmutableVirtualWorld { + private final WorldData worldData; + + private final Long2ObjectOpenHashMap> files; + private final Long2ObjectOpenHashMap chunks; // TODO use soft references OR clear chunks outside view distance + + private final MutableBlockVector2D lastPos = new MutableBlockVector2D(); + private final FawePlayer player; + private final Location origin; + private final BlockVector2D chunkOffset; + + public SchemVis(FawePlayer player) { + this.files = new Long2ObjectOpenHashMap<>(); + this.chunks = new Long2ObjectOpenHashMap<>(); + this.player = player; + this.worldData = player.getWorld().getWorldData(); + + // Set the origin to somewhere around where the player currently is + FaweLocation pos = player.getLocation(); + this.origin = player.getPlayer().getLocation(); + this.chunkOffset = new BlockVector2D(pos.x >> 4,pos.z >> 4); + } + + @Override + public void handlePlayerInput(Player player, PlayerInputEvent event) { + int range = 240; + BlockWorldVector target = new TargetBlock(player, range, 0.2).getAnyTargetBlock(); + if (target != null) { + int chunkX = target.getBlockX() >> 4; + int chunkZ = target.getBlockZ() >> 4; + long pos = MathMan.pairInt(chunkX, chunkZ); + Map.Entry entry = files.get(pos); + if (entry != null) { + File cachedFile = entry.getKey(); + String filename = cachedFile.getName(); + // get non `.cached` file + File file = new File(cachedFile.getParentFile(), filename.substring(1, filename.length() - 7)); + URI uri = file.toURI(); + + ClipboardFormat format = ClipboardFormat.findByFile(file); + if (format != null) { + LocalSession session = this.player.getSession(); + try { + synchronized (this) { + switch (event.getInputType()) { + case PRIMARY: // set clipboard + format.hold(player, uri, new FileInputStream(file)); + BBC.SCHEMATIC_LOADED.send(player, filename); + session.setVirtualWorld(null); + break; + case SECONDARY: // add/remove clipboard + ClipboardHolder existing = session.getExistingClipboard(); + + boolean contains = existing instanceof URIClipboardHolder && ((URIClipboardHolder) existing).contains(uri); + if (contains) { + // Remove it + if (existing instanceof MultiClipboardHolder) { + MultiClipboardHolder multi = ((MultiClipboardHolder) existing); + multi.remove(uri); + if (multi.getClipboards().isEmpty()) session.setClipboard(null); + } else { + session.setClipboard(null); + } + BBC.CLIPBOARD_CLEARED.send(player); + } else { + // Add it + ByteSource source = Files.asByteSource(file); + MultiClipboardHolder multi = new MultiClipboardHolder(URI.create(""), worldData, new LazyClipboardHolder(uri, source, format, worldData, null)); + session.addClipboard(multi); + BBC.SCHEMATIC_LOADED.send(player, filename); + } + + // Resend relevant chunks + FaweQueue packetQueue = SetQueue.IMP.getNewQueue(this.player.getWorld(), true, false); + if (packetQueue.supports(Capability.CHUNK_PACKETS)) { + ArrayDeque toSend = new ArrayDeque<>(); + int OX = chunkOffset.getBlockX(); + int OZ = chunkOffset.getBlockZ(); + ObjectIterator> iter = chunks.long2ObjectEntrySet().fastIterator(); + while (iter.hasNext()) { + Long2ObjectMap.Entry mcaChunkEntry = iter.next(); + long curChunkPos = mcaChunkEntry.getLongKey(); + Map.Entry curFileEntry = files.get(curChunkPos); + if (curFileEntry != null && curFileEntry == entry) { + MCAChunk chunk = mcaChunkEntry.getValue(); + if (contains) { + iter.remove(); + } + else select(chunk); + toSend.add(curChunkPos); + } + } + for (long curChunkPos : toSend) send(packetQueue, MathMan.unpairIntX(curChunkPos), MathMan.unpairIntY(curChunkPos)); + } + + break; + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + } + } + + /** + * Discard chunks outside FOV + */ + private void clean() { + if (chunks.size() > 225) { + TaskManager.IMP.sync(() -> { + if (chunks.size() > 225) { + synchronized (SchemVis.this) { + FaweLocation pos = player.getLocation(); + int centerX = pos.x >> 4; + int centerZ = pos.z >> 4; + ObjectIterator> iter = chunks.long2ObjectEntrySet().fastIterator(); + while (iter.hasNext()) { + Long2ObjectMap.Entry entry = iter.next(); + long pair = entry.getLongKey(); + int chunkX = MathMan.unpairIntX(pair); + int chunkZ = MathMan.unpairIntY(pair); + if (Math.abs(centerX - chunkX) > 15 || Math.abs(centerZ - chunkZ) > 15) { + iter.remove(); + } + } + } + } + return null; + }); + } + } + + /** + * Send a chunk + * @param packetQueue + * @param chunkX + * @param chunkZ + */ + private void send(FaweQueue packetQueue, int chunkX, int chunkZ) { + TaskManager.IMP.getPublicForkJoinPool().submit(() -> { + try { + int OX = chunkOffset.getBlockX(); + int OZ = chunkOffset.getBlockZ(); + FaweChunk toSend = getSnapshot(chunkX, chunkZ); + toSend.setLoc(SchemVis.this, chunkX + OX, chunkZ + OZ); + packetQueue.sendChunkUpdate(toSend, SchemVis.this.player); + } catch (Throwable e) { + e.printStackTrace(); + } + }); + } + + /** + * The offset for this virtual world + * @return offset vector + */ + @Override + public Vector getOrigin() { + return new BlockVector(chunkOffset.getBlockX() << 4, 0, chunkOffset.getBlockZ() << 4); + } + + /** + * @param chunkX + * @param chunkZ + * @return The schematic file visualized at a chunk position + */ + private File getFile(int chunkX, int chunkZ) { + long pair = MathMan.pairInt(chunkX, chunkZ); + Map.Entry entry = files.get(pair); + return entry != null ? entry.getKey() : null; + } + + private Map.Entry getEntry(File file, long position) { + return new AbstractMap.SimpleEntry(file, position); + } + + /** + * Replace the blocks with glass, to indicate it's been selected + * @param chunk + */ + private void select(MCAChunk chunk) { + for (int layer = 0; layer < 16; layer++) { + byte[] ids = chunk.ids[layer]; + if (ids != null) { + for (int i = 0; i < ids.length; i++) { + if (ids[i] != 0) ids[i] = BlockID.STAINED_GLASS; + Arrays.fill(chunk.data[layer], (byte) 0); + } + } + } + } + + /** + * Cache a chunk + * @param file + * @param chunk + */ + private void cacheChunk(File file, MCAChunk chunk, boolean selected) { + long pair = MathMan.pairInt(chunk.getX(), chunk.getZ()); + // Light chunk + for (int layer = 0; layer < 16; layer++) { + if (chunk.skyLight[layer] != null) { + Arrays.fill(chunk.skyLight[layer], (byte) 255); + } + } + if (selected) { + select(chunk); + } + synchronized (this) { + chunks.put(pair, chunk); + } + } + + /** + * Get the next free position for a schematic of the provided dimensions + * @param schemDimensions + * @return + */ + private BlockVector2D registerAndGetChunkOffset(BlockVector schemDimensions, File file) { + int chunkX = schemDimensions.getBlockX() >> 4; + int chunkZ = schemDimensions.getBlockZ() >> 4; + MutableBlockVector2D pos2 = new MutableBlockVector2D(); + MutableBlockVector2D curPos = lastPos; + // Find next free position + while (!isAreaFree(curPos, pos2.setComponents(curPos.getBlockX() + chunkX, curPos.getBlockZ() + chunkZ))) { + if (curPos == lastPos && !files.containsKey(MathMan.pairInt(curPos.getBlockX(), curPos.getBlockZ()))) { + curPos = new MutableBlockVector2D(); + curPos.setComponents(lastPos.getBlockX(), lastPos.getBlockZ()); + } + curPos.nextPosition(); + } + // Register the chunks + Map.Entry originValue = getEntry(file, MathMan.pairInt(curPos.getBlockX(), curPos.getBlockZ())); + for (int x = 0; x <= chunkX; x++) { + for (int z = 0; z <= chunkZ; z++) { + long pos = MathMan.pairInt(curPos.getBlockX() + x, curPos.getBlockZ() + z); + files.put(pos, originValue); + } + } + return curPos; + } + + private boolean isAreaFree(BlockVector2D chunkPos1, BlockVector2D chunkPos2 /* inclusive */) { + for (int x = chunkPos1.getBlockX(); x <= chunkPos2.getBlockX(); x++) { + for (int z = chunkPos1.getBlockZ(); z <= chunkPos2.getBlockZ(); z++) { + if (files.containsKey(MathMan.pairInt(x, z)) || (x == 0 && z == 0)) return false; + } + } + return true; + } + + private boolean isSelected(File file) { + ClipboardHolder clipboard = player.getSession().getExistingClipboard(); + if (clipboard != null) { + if (clipboard instanceof URIClipboardHolder) { + return ((URIClipboardHolder) clipboard).contains(file.toURI()); + } + } + return false; + } + + public void add(File file) throws IOException { + File cached = new File(file.getParentFile(), "." + file.getName() + ".cached"); + if (cached.exists() && file.lastModified() <= cached.lastModified()) { + try (FileInputStream fis = new FileInputStream(cached)) { + BlockVector dimensions = new BlockVector(IOUtil.readVarInt(fis), IOUtil.readVarInt(fis), IOUtil.readVarInt(fis)); + BlockVector2D offset = registerAndGetChunkOffset(dimensions, cached); + } + } else { + try { + player.sendMessage(BBC.getPrefix() + "Converting: " + file); + cached.createNewFile(); + try (FileInputStream in = new FileInputStream(file)) { + ClipboardFormat format = ClipboardFormat.findByFile(file); + ClipboardReader reader = format.getReader(in); + Clipboard clipboard = reader.read(worldData); + clipboard.setOrigin(clipboard.getMinimumPoint()); + try { + MCAQueue queue = new MCAQueue(null, null, false); + BlockVector dimensions = clipboard.getDimensions().toBlockVector(); + BlockVector2D offset = registerAndGetChunkOffset(dimensions, cached); + new Schematic(clipboard).paste(queue, Vector.ZERO, true); + try (FileOutputStream fos = new FileOutputStream(cached)) { + IOUtil.writeVarInt(fos, dimensions.getBlockX()); + IOUtil.writeVarInt(fos, dimensions.getBlockY()); + IOUtil.writeVarInt(fos, dimensions.getBlockZ()); + + try (FaweOutputStream cos = MainUtil.getCompressedOS(fos, 2)) { + NBTOutputStream nos = new NBTOutputStream((DataOutput) cos); + Collection writeChunks = queue.getFaweChunks(); + cos.writeInt(writeChunks.size()); + + boolean selected = isSelected(file); + + for (FaweChunk chunk : writeChunks) { + MCAChunk mcaChunk = ((MCAChunk) chunk); + mcaChunk.write(nos); + mcaChunk.setLoc(this, mcaChunk.getX() + offset.getBlockX(), mcaChunk.getZ() + offset.getBlockZ()); + if (Math.abs(mcaChunk.getX()) <= 15 && Math.abs(mcaChunk.getZ()) <= 15) { + cacheChunk(cached, mcaChunk, selected); + } + } + } + } + if (System.getProperty("os.name").contains("Windows")) { + Path path = cached.toPath(); + Object hidden = java.nio.file.Files.getAttribute(path, "dos:hidden", LinkOption.NOFOLLOW_LINKS); + if (hidden != null) { + //link file to DosFileAttributes + java.nio.file.Files.setAttribute(path, "dos:hidden", Boolean.TRUE, LinkOption.NOFOLLOW_LINKS); + } + } + } finally { + if (clipboard instanceof Closeable) { + ((Closeable) clipboard).close(); + } + } + } + } catch (Throwable e) { + e.printStackTrace(); + cached.delete(); + } + } + } + + private synchronized MCAChunk getCachedChunk(int chunkX, int chunkZ) { + return chunks.get(MathMan.pairInt(chunkX, chunkZ)); + } + + private MCAChunk getChunk(int chunkX, int chunkZ) { + long pair = MathMan.pairInt(chunkX, chunkZ); + // Check cached + MCAChunk chunk = getCachedChunk(chunkX, chunkZ); + if (chunk != null) return chunk; + + // We need to cache it + Map.Entry entry = files.get(pair); + if (entry != null) { + File cached = entry.getKey(); + + // Guard caching by other threads + synchronized (cached) { + chunk = getCachedChunk(chunkX, chunkZ); + + // Read chunks from disk + if (chunk == null) { + clean(); + String filename = cached.getName(); + File file = new File(cached.getParentFile(), filename.substring(1, filename.length() - 7)); + boolean selected = isSelected(file); + + long origin = entry.getValue(); + int OCX = MathMan.unpairIntX(origin); + int OCZ = MathMan.unpairIntY(origin); + try { + try (FileInputStream fis = new FileInputStream(cached)) { + BlockVector dimensions = new BlockVector(IOUtil.readVarInt(fis), IOUtil.readVarInt(fis), IOUtil.readVarInt(fis)); + try (FaweInputStream in = MainUtil.getCompressedIS(fis)) { + try (NBTInputStream nis = new NBTInputStream(in)) { + + int numChunks = in.readInt(); + for (int i = 0; i < numChunks; i++) { + MCAChunk mcaChunk = new MCAChunk(nis, null, 0, 0, true); + mcaChunk.setLoc(this, mcaChunk.getX() + OCX, mcaChunk.getZ() + OCZ); + cacheChunk(cached, mcaChunk, selected); + } + } + } + } + } catch (IOException e) { + e.printStackTrace(); + } + // Return the cached chunk, or an empty one + chunk = getCachedChunk(chunkX, chunkZ); + if (chunk == null) { + // TODO use shared chunk + // TODO synchronize on sending chunk packet + cacheChunk(cached, chunk = new MCAChunk(this, chunkX, chunkZ), selected); + } + } + } + } else { + chunk = new MCAChunk(this, chunkX, chunkZ); + } + return chunk; + } + + /** + * Return a lazily evaluated chunk + * @param chunkX + * @param chunkZ + * @return lazy chunk + */ + @Override + public FaweChunk getSnapshot(int chunkX, int chunkZ) { + return new LazyFaweChunk(this, chunkX, chunkZ) { + @Override + public MCAChunk getChunk() { + MCAChunk tmp = SchemVis.this.getChunk(chunkX, chunkZ); + tmp.setLoc(SchemVis.this, getX(), getZ()); + return tmp; + } + + @Override + public void addToQueue() { + MCAChunk cached = getCachedChunk(); + if (cached != null) setChunk(cached); + } + }; + } + + @Override + public WorldData getWorldData() { + return worldData; + } + + @Override + public FawePlayer getPlayer() { + return player; + } + + public void bind() { + player.setVirtualWorld(this); + } + + /** + * Send all chunks to the player + */ + @Override + public void update() { + FaweQueue packetQueue = SetQueue.IMP.getNewQueue(player.getWorld(), true, false); + + if (!packetQueue.supports(Capability.CHUNK_PACKETS)) { + return; + } + + int OX = chunkOffset.getBlockX(); + int OZ = chunkOffset.getBlockZ(); + + FaweLocation position = player.getLocation(); + int pcx = (position.x >> 4) - OX; + int pcz = (position.z >> 4) - OZ; + + int scx = pcx - 15; + int scz = pcz - 15; + int ecx = pcx + 15; + int ecz = pcz + 15; + + for (int cz = scz; cz <= ecz; cz++) { + for (int cx = scx; cx <= ecx; cx++) { + final int finalCX = cx; + final int finalCZ = cz; + send(packetQueue, finalCX, finalCZ); + } + } + } + + @Override + public void sendChunk(FaweChunk chunk) { /* do nothing - never used */ } + + @Override + public void sendChunk(int x, int z, int bitMask) { /* do nothing - never used*/ } + + @Override + public int getBiomeId(int x, int z) throws FaweException.FaweChunkLoadException { + // TODO later (currently not used) + return 0; + } + + @Override + public int getCombinedId4Data(int x, int y, int z) throws FaweException.FaweChunkLoadException { + MCAChunk chunk = getChunk(x >> 4, z >> 4); + if (y < 0 || y > 255) return 0; + return chunk.getBlockCombinedId(x & 15, y, z & 15); + } + + /** + * Closes this virtual world and sends the normal world chunks to the player + * @throws IOException + */ + @Override + public synchronized void close(boolean update) throws IOException { + clear(); + chunks.clear(); + files.clear(); + player.getPlayer().setPosition(origin.toVector(), origin.getPitch(), origin.getYaw()); + if (update) { + FaweQueue packetQueue = SetQueue.IMP.getNewQueue(player.getWorld(), true, false); + + int OX = chunkOffset.getBlockX(); + int OZ = chunkOffset.getBlockZ(); + + FaweLocation position = player.getLocation(); + int pcx = (position.x >> 4) - OX; + int pcz = (position.z >> 4) - OZ; + + int scx = pcx - 15; + int scz = pcz - 15; + int ecx = pcx + 15; + int ecz = pcz + 15; + + for (int cz = scz; cz <= ecz; cz++) { + for (int cx = scx; cx <= ecx; cx++) { + packetQueue.sendChunk(cx + OX, cz + OZ, 0); + } + } + } + } +} diff --git a/core/src/main/java/com/boydti/fawe/util/IOUtil.java b/core/src/main/java/com/boydti/fawe/util/IOUtil.java new file mode 100644 index 00000000..3ad0a8d4 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/util/IOUtil.java @@ -0,0 +1,56 @@ +package com.boydti.fawe.util; + +import java.io.*; +import java.net.URI; + +public final class IOUtil { + public InputStream toInputStream(URI uri) throws IOException { + String scheme = uri.getScheme(); + switch (scheme.toLowerCase()) { + case "file": + return new FileInputStream(uri.getPath()); + case "http": + case "https": + return uri.toURL().openStream(); + default: + return null; + } + } + + public static final int readInt(InputStream in) throws IOException { + int ch1 = in.read(); + int ch2 = in.read(); + int ch3 = in.read(); + int ch4 = in.read(); + if ((ch1 | ch2 | ch3 | ch4) < 0) + throw new EOFException(); + return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0)); + } + + public static final void writeInt(OutputStream out, int v) throws IOException { + out.write((v >>> 24) & 0xFF); + out.write((v >>> 16) & 0xFF); + out.write((v >>> 8) & 0xFF); + out.write((v >>> 0) & 0xFF); + } + + public static void writeVarInt(OutputStream out, int i) throws IOException { + while((i & -128) != 0) { + out.write(i & 127 | 128); + i >>>= 7; + } + out.write(i); + } + + public static int readVarInt(InputStream in) throws IOException { + int i = 0; + int offset = 0; + int b; + while ((b = in.read()) > 127) { + i |= (b - 128) << offset; + offset += 7; + } + i |= b << offset; + return i; + } +} diff --git a/core/src/main/java/com/sk89q/worldedit/BlockVector.java b/core/src/main/java/com/sk89q/worldedit/BlockVector.java index 20965c52..9f007c12 100644 --- a/core/src/main/java/com/sk89q/worldedit/BlockVector.java +++ b/core/src/main/java/com/sk89q/worldedit/BlockVector.java @@ -19,6 +19,8 @@ package com.sk89q.worldedit; +import java.io.IOException; + /** * Extension of {@code Vector} that that compares with other instances * using integer components. @@ -93,6 +95,19 @@ public class BlockVector extends Vector { return ((int) getX() ^ ((int) getZ() << 16)) ^ ((int) getY() << 30); } + private void writeObject(java.io.ObjectOutputStream stream) throws IOException { + if (!(this instanceof MutableBlockVector)) { + stream.writeInt(getBlockX()); + stream.writeInt(getBlockY()); + stream.writeInt(getBlockZ()); + } + } + + private void readObject(java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException { + if (this instanceof MutableBlockVector) return; + this.setComponents(stream.readInt(), stream.readInt(), stream.readInt()); + } + @Override public BlockVector toBlockVector() { return this; diff --git a/core/src/main/java/com/sk89q/worldedit/EditSession.java b/core/src/main/java/com/sk89q/worldedit/EditSession.java index c04f7af0..6c79defc 100644 --- a/core/src/main/java/com/sk89q/worldedit/EditSession.java +++ b/core/src/main/java/com/sk89q/worldedit/EditSession.java @@ -25,12 +25,12 @@ import com.boydti.fawe.FaweCache; import com.boydti.fawe.config.BBC; import com.boydti.fawe.config.Settings; import com.boydti.fawe.example.MappedFaweQueue; -import com.boydti.fawe.jnbt.anvil.HeightMapMCAGenerator; import com.boydti.fawe.jnbt.anvil.MCAQueue; import com.boydti.fawe.jnbt.anvil.MCAWorld; import com.boydti.fawe.logging.LoggingChangeSet; import com.boydti.fawe.logging.rollback.RollbackOptimizedHistory; import com.boydti.fawe.object.*; +import com.boydti.fawe.object.brush.visualization.VirtualWorld; import com.boydti.fawe.object.changeset.*; import com.boydti.fawe.object.clipboard.WorldCopyClipboard; import com.boydti.fawe.object.collection.LocalBlockVectorSet; @@ -297,7 +297,7 @@ public class EditSession extends AbstractDelegateExtent implements HasFaweQueue, } } if (allowedRegions == null) { - if (player != null && !player.hasPermission("fawe.bypass") && !player.hasPermission("fawe.bypass.regions") && !(queue instanceof HeightMapMCAGenerator)) { + if (player != null && !player.hasPermission("fawe.bypass") && !player.hasPermission("fawe.bypass.regions") && !(queue instanceof VirtualWorld)) { allowedRegions = player.getCurrentRegions(); } } diff --git a/core/src/main/java/com/sk89q/worldedit/LocalSession.java b/core/src/main/java/com/sk89q/worldedit/LocalSession.java index d3361d49..b7938f9a 100644 --- a/core/src/main/java/com/sk89q/worldedit/LocalSession.java +++ b/core/src/main/java/com/sk89q/worldedit/LocalSession.java @@ -26,9 +26,11 @@ import com.boydti.fawe.object.FaweInputStream; import com.boydti.fawe.object.FaweLimit; import com.boydti.fawe.object.FaweOutputStream; import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.object.brush.visualization.VirtualWorld; import com.boydti.fawe.object.changeset.AnvilHistory; import com.boydti.fawe.object.changeset.DiskStorageHistory; import com.boydti.fawe.object.changeset.FaweChangeSet; +import com.boydti.fawe.object.clipboard.MultiClipboardHolder; import com.boydti.fawe.object.collection.SparseBitSet; import com.boydti.fawe.object.extent.ResettableExtent; import com.boydti.fawe.util.EditSessionBuilder; @@ -41,12 +43,7 @@ import com.sk89q.jchronic.Options; import com.sk89q.jchronic.utils.Span; import com.sk89q.jchronic.utils.Time; import com.sk89q.worldedit.blocks.BaseBlock; -import com.sk89q.worldedit.command.tool.BlockTool; -import com.sk89q.worldedit.command.tool.BrushHolder; -import com.sk89q.worldedit.command.tool.BrushTool; -import com.sk89q.worldedit.command.tool.InvalidToolBindException; -import com.sk89q.worldedit.command.tool.SinglePickaxe; -import com.sk89q.worldedit.command.tool.Tool; +import com.sk89q.worldedit.command.tool.*; import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.extent.inventory.BlockBag; @@ -65,21 +62,10 @@ import com.sk89q.worldedit.session.ClipboardHolder; import com.sk89q.worldedit.session.request.Request; import com.sk89q.worldedit.world.World; import com.sk89q.worldedit.world.snapshot.Snapshot; -import java.io.File; -import java.io.FileFilter; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.Calendar; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; -import java.util.TimeZone; -import java.util.UUID; +import java.io.*; +import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; +import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -138,6 +124,8 @@ public class LocalSession { private transient UUID uuid; private transient volatile long historySize = 0; + private transient VirtualWorld virtual; + // Saved properties private String lastScript; private RegionSelectorType defaultSelector; @@ -733,6 +721,29 @@ public class LocalSession { return selector.getRegion(); } + public synchronized @Nullable VirtualWorld getVirtualWorld() { + return virtual; + } + + public void setVirtualWorld(@Nullable VirtualWorld world) { + VirtualWorld tmp; + synchronized (this) { + tmp = this.virtual; + this.virtual = world; + } + if (tmp != null) { + try { + tmp.close(world == null); + } catch (IOException e) { + e.printStackTrace(); + } + } + if (world != null) { + Fawe.imp().registerPacketListener(); + world.update(); + } + } + /** * Get the selection world. * @@ -785,6 +796,24 @@ public class LocalSession { return clipboard; } + public void addClipboard(@Nonnull MultiClipboardHolder toAppend) { + checkNotNull(toAppend); + ClipboardHolder existing = getExistingClipboard(); + MultiClipboardHolder multi; + if (existing instanceof MultiClipboardHolder) { + multi = (MultiClipboardHolder) existing; + for (ClipboardHolder holder : toAppend.getHolders()) { + multi.add(holder); + } + } else { + multi = toAppend; + if (existing != null) { + multi.add(existing); + } + } + setClipboard(multi); + } + /** * Sets the clipboard. *

diff --git a/core/src/main/java/com/sk89q/worldedit/MutableBlockVector2D.java b/core/src/main/java/com/sk89q/worldedit/MutableBlockVector2D.java index 8b10c4dc..4141fd6b 100644 --- a/core/src/main/java/com/sk89q/worldedit/MutableBlockVector2D.java +++ b/core/src/main/java/com/sk89q/worldedit/MutableBlockVector2D.java @@ -78,4 +78,33 @@ public final class MutableBlockVector2D extends BlockVector2D implements Seriali this.x = stream.readInt(); this.z = stream.readInt(); } + + public MutableBlockVector2D nextPosition() { + int absX = Math.abs(x); + int absY = Math.abs(z); + if (absX > absY) { + if (x > 0) { + return setComponents(x, z + 1); + } else { + return setComponents(x, z - 1); + } + } else if (absY > absX) { + if (z > 0) { + return setComponents(x - 1, z); + } else { + return setComponents(x + 1, z); + } + } else { + if (x == z && x > 0) { + return setComponents(x, z + 1); + } + if (x == absX) { + return setComponents(x, z + 1); + } + if (z == absY) { + return setComponents(x, z - 1); + } + return setComponents(x + 1, z); + } + } } diff --git a/core/src/main/java/com/sk89q/worldedit/Vector.java b/core/src/main/java/com/sk89q/worldedit/Vector.java index 6c69e83f..21052a4d 100644 --- a/core/src/main/java/com/sk89q/worldedit/Vector.java +++ b/core/src/main/java/com/sk89q/worldedit/Vector.java @@ -889,7 +889,7 @@ public class Vector implements Comparable, Serializable { } private void writeObject(java.io.ObjectOutputStream stream) throws IOException { - if (!(this instanceof Vector)) { + if (!(this instanceof MutableBlockVector)) { stream.writeDouble(x); stream.writeDouble(y); stream.writeDouble(z); diff --git a/core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java b/core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java index 318e235e..8e5872ee 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java +++ b/core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java @@ -117,7 +117,7 @@ public class GenerationCommands extends MethodCommands { @Command( aliases = {"/image", "/img"}, desc = "Generate an image", - usage = " [randomize=true] [complexity=100]", + usage = " [randomize=true] [complexity=100] [dimensions]", min = 1, max = 3 ) 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 70fafc5a..4fc07b58 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java +++ b/core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java @@ -29,6 +29,7 @@ import com.boydti.fawe.object.clipboard.MultiClipboardHolder; import com.boydti.fawe.object.clipboard.URIClipboardHolder; import com.boydti.fawe.object.clipboard.remap.ClipboardRemapper; import com.boydti.fawe.object.schematic.StructureFormat; +import com.boydti.fawe.object.schematic.visualizer.SchemVis; import com.boydti.fawe.util.MainUtil; import com.boydti.fawe.util.chat.Message; import com.sk89q.minecraft.util.commands.Command; @@ -44,9 +45,7 @@ import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat; -import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader; import com.sk89q.worldedit.extent.clipboard.io.ClipboardWriter; -import com.sk89q.worldedit.extent.clipboard.io.SchematicReader; import com.sk89q.worldedit.function.operation.Operations; import com.sk89q.worldedit.math.transform.Transform; import com.sk89q.worldedit.session.ClipboardHolder; @@ -54,18 +53,15 @@ import com.sk89q.worldedit.util.command.binding.Switch; import com.sk89q.worldedit.util.command.parametric.Optional; import com.sk89q.worldedit.util.io.file.FilenameException; import com.sk89q.worldedit.world.registry.WorldData; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; +import java.io.*; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; import java.nio.file.Files; -import java.util.UUID; +import java.util.*; +import java.util.concurrent.atomic.LongAdder; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; @@ -110,20 +106,7 @@ public class SchematicCommands extends MethodCommands { MultiClipboardHolder all = format.loadAllFromInput(player, wd, filename, true); if (all != null) { - ClipboardHolder existing = session.getExistingClipboard(); - MultiClipboardHolder multi; - if (existing instanceof MultiClipboardHolder) { - multi = (MultiClipboardHolder) existing; - for (ClipboardHolder holder : all.getHolders()) { - multi.add(holder); - } - } else { - multi = all; - if (existing != null) { - multi.add(existing); - } - } - session.setClipboard(multi); + session.addClipboard(all); BBC.SCHEMATIC_LOADED.send(player, filename); } } catch (IOException e) { @@ -274,18 +257,7 @@ public class SchematicCommands extends MethodCommands { uri = f.toURI(); } - final ClipboardReader reader = format.getReader(in); - final WorldData worldData = player.getWorld().getWorldData(); - final Clipboard clipboard; - session.setClipboard(null); - if (reader instanceof SchematicReader) { - clipboard = ((SchematicReader) reader).read(player.getWorld().getWorldData(), player.getUniqueId()); - } else if (reader instanceof StructureFormat) { - clipboard = ((StructureFormat) reader).read(player.getWorld().getWorldData(), player.getUniqueId()); - } else { - clipboard = reader.read(player.getWorld().getWorldData()); - } - session.setClipboard(new URIClipboardHolder(uri, clipboard, worldData)); + format.hold(player, uri, in); BBC.SCHEMATIC_LOADED.send(player, filename); } catch (IllegalArgumentException e) { player.printError("Unknown filename: " + filename); @@ -380,24 +352,107 @@ public class SchematicCommands extends MethodCommands { } } - @Command(aliases = {"delete", "d"}, usage = "", desc = "Delete a saved schematic", help = "Delete a schematic from the schematic list", min = 1, max = 1) - @CommandPermissions("worldedit.schematic.delete") - public void delete(final Player player, final LocalSession session, final CommandContext args) throws WorldEditException { + @Command(aliases = {"move", "m"}, usage = "", desc = "Move your loaded schematic", help = "Move your currently loaded schematics", min = 1, max = 1) + @CommandPermissions({"worldedit.schematic.move", "worldedit.schematic.move.other"}) + public void move(final Player player, final LocalSession session, final CommandContext args) throws WorldEditException { final LocalConfiguration config = this.worldEdit.getConfiguration(); - final String filename = args.getString(0); - final File working = this.worldEdit.getWorkingDirectoryFile(config.saveDir); final File dir = Settings.IMP.PATHS.PER_PLAYER_SCHEMATICS ? new File(working, player.getUniqueId().toString()) : working; - final File f = this.worldEdit.getSafeSaveFile(player, dir, filename, "schematic", "schematic"); - if (!f.exists()) { - player.printError("Schematic " + filename + " does not exist!"); + File destDir = new File(dir, args.getString(0)); + if (!MainUtil.isInSubDirectory(working, destDir)) { + player.printError("Directory " + destDir + " does not exist!"); return; } - if (!f.delete()) { - player.printError("Deletion of " + filename + " failed! Maybe it is read-only."); + if (Settings.IMP.PATHS.PER_PLAYER_SCHEMATICS && !MainUtil.isInSubDirectory(dir, destDir) && !player.hasPermission("worldedit.schematic.move.other")) { + BBC.NO_PERM.send(player, "worldedit.schematic.move.other"); return; } - BBC.FILE_DELETED.send(player, filename); + ClipboardHolder clipboard = session.getClipboard(); + List sources = getFiles(clipboard); + if (sources.isEmpty()) { + BBC.SCHEMATIC_NONE.send(player); + return; + } + if (!destDir.exists() && !destDir.mkdirs()) { + player.printError("Creation of " + destDir + " failed! (check file permissions)"); + return; + } + for (File source : sources) { + File destFile = new File(destDir, source.getName()); + if (destFile.exists()) { + BBC.SCHEMATIC_MOVE_EXISTS.send(player, destFile); + continue; + } + if (Settings.IMP.PATHS.PER_PLAYER_SCHEMATICS && (!MainUtil.isInSubDirectory(dir, destFile) || !MainUtil.isInSubDirectory(dir, source)) && !player.hasPermission("worldedit.schematic.delete.other")) { + BBC.SCHEMATIC_MOVE_FAILED.send(player, destFile, BBC.NO_PERM.f("worldedit.schematic.move.other")); + continue; + } + try { + File cached = new File(source.getParentFile(), "." + source.getName() + ".cached"); + Files.move(source.toPath(), destFile.toPath()); + if (cached.exists()) Files.move(cached.toPath(), destFile.toPath()); + BBC.SCHEMATIC_MOVE_SUCCESS.send(player, source, destFile); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + @Command(aliases = {"delete", "d"}, usage = "", desc = "Delete a saved schematic", help = "Delete a schematic from the schematic list", min = 1, max = 1) + @CommandPermissions({"worldedit.schematic.delete", "worldedit.schematic.other"}) + public void delete(final Player player, final LocalSession session, final CommandContext args) throws WorldEditException { + final LocalConfiguration config = this.worldEdit.getConfiguration(); + final File working = this.worldEdit.getWorkingDirectoryFile(config.saveDir); + final File dir = Settings.IMP.PATHS.PER_PLAYER_SCHEMATICS ? new File(working, player.getUniqueId().toString()) : working; + List files = new ArrayList<>(); + + final String filename = args.getString(0); + + if (filename.equalsIgnoreCase("*")) { + files.addAll(getFiles(session.getClipboard())); + } else { + files.add(new File(dir, filename)); + } + if (files.isEmpty()) { + BBC.SCHEMATIC_NONE.send(player); + return; + } + for (File f : files) { + if (!MainUtil.isInSubDirectory(working, f) || !f.exists()) { + player.printError("Schematic " + filename + " does not exist! (" + f.exists() + "|" + f + "|" + (!MainUtil.isInSubDirectory(working, f)) + ")"); + continue; + } + if (Settings.IMP.PATHS.PER_PLAYER_SCHEMATICS && !MainUtil.isInSubDirectory(dir, f) && !player.hasPermission("worldedit.schematic.delete.other")) { + BBC.NO_PERM.send(player, "worldedit.schematic.delete.other"); + continue; + } + if (!delete(f)) { + player.printError("Deletion of " + filename + " failed! Maybe it is read-only."); + continue; + } + BBC.FILE_DELETED.send(player, filename); + } + } + + private List getFiles(ClipboardHolder clipboard) { + List files = new ArrayList<>(); + Collection uris = Collections.emptyList(); + if (clipboard instanceof URIClipboardHolder) { + uris = ((URIClipboardHolder) clipboard).getURIs(); + } + for (URI uri : uris) { + File file = new File(uri); + if (file.exists()) files.add(file); + } + return files; + } + + private boolean delete(File file) { + if (file.delete()) { + new File(file.getParentFile(), "." + file.getName() + ".cached").delete(); + return true; + } + return false; } @Command(aliases = {"formats", "listformats", "f"}, desc = "List available formats", max = 0) @@ -424,6 +479,58 @@ public class SchematicCommands extends MethodCommands { m.send(actor); } + + @Command( + aliases = {"show"}, + desc = "Show a schematic", + usage = "[global|mine|] [page=1]", + min = 0, + max = -1, + flags = "dnp", + help = "List all schematics in the schematics directory\n" + + " -p prints the requested page\n" + + " -f restricts by format\n" + ) + @CommandPermissions("worldedit.heightmap.list") + public void show(Player player, CommandContext args, @Switch('p') @Optional("1") int page) { + FawePlayer fp = FawePlayer.wrap(player); + if (args.argsLength() == 0 && fp.getSession().getVirtualWorld() != null) { + fp.setVirtualWorld(null); + return; + } + LocalConfiguration config = worldEdit.getConfiguration(); + File dir = worldEdit.getWorkingDirectoryFile(config.saveDir); + try { + SchemVis visExtent = new SchemVis(fp); + LongAdder count = new LongAdder(); + UtilityCommands.getFiles(dir, player, args, 0, Character.MAX_VALUE, null, Settings.IMP.PATHS.PER_PLAYER_SCHEMATICS, file -> { + if (file.isFile()) { + try { + visExtent.add(file); + count.add(1); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + }); + visExtent.bind(); + visExtent.update(); + + String cmdPrefix = "/" + (config.noDoubleSlash ? "" : "/"); + String cmdShow = Commands.getAlias(ClipboardCommands.class, "schematic") + " " + Commands.getAlias(ClipboardCommands.class, "show"); + BBC.SCHEMATIC_SHOW.send(fp, count.longValue(), args.getJoinedStrings(0), cmdShow); + + if (fp.getSession().getExistingClipboard() != null) { + String cmd = cmdPrefix + Commands.getAlias(ClipboardCommands.class, "clipboard") + " " + Commands.getAlias(ClipboardCommands.class, "clear"); + BBC.SCHEMATIC_PROMPT_CLEAR.send(fp, cmd); + } + + } catch (Throwable e) { + fp.setVirtualWorld(null); + throw e; + } + } + @Command( aliases = {"list", "ls", "all"}, desc = "List saved schematics", @@ -454,7 +561,7 @@ public class SchematicCommands extends MethodCommands { URIClipboardHolder multi = as(URIClipboardHolder.class, fp.getSession().getExistingClipboard()); - UtilityCommands.list(dir, actor, args, page, formatName, Settings.IMP.PATHS.PER_PLAYER_SCHEMATICS, new RunnableVal3() { + UtilityCommands.list(dir, actor, args, page, -1, formatName, Settings.IMP.PATHS.PER_PLAYER_SCHEMATICS, new RunnableVal3() { @Override public void run(Message msg, URI uri, String relFilePath) { boolean isDir = false; diff --git a/core/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java b/core/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java index 7772fdb3..caeca524 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java +++ b/core/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java @@ -24,6 +24,7 @@ import com.boydti.fawe.FaweAPI; import com.boydti.fawe.command.FaweParser; import com.boydti.fawe.config.BBC; import com.boydti.fawe.config.Commands; +import com.boydti.fawe.object.DelegateConsumer; import com.boydti.fawe.object.FaweLimit; import com.boydti.fawe.object.FawePlayer; import com.boydti.fawe.object.RunnableVal3; @@ -74,8 +75,8 @@ import java.lang.reflect.Type; import java.net.URI; import java.util.*; import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; -import java.util.stream.Collectors; import static com.sk89q.minecraft.util.commands.Logging.LogMode.PLACEMENT; @@ -619,7 +620,7 @@ public class UtilityCommands extends MethodCommands { } public static void list(File dir, Actor actor, CommandContext args, @Range(min = 0) int page, String formatName, boolean playerFolder, String onClickCmd) { - list(dir, actor, args, page, formatName, playerFolder, new RunnableVal3() { + list(dir, actor, args, page, -1, formatName, playerFolder, new RunnableVal3() { @Override public void run(Message m, URI uri, String fileName) { m.text(BBC.SCHEMATIC_LIST_ELEM, fileName, ""); @@ -628,7 +629,75 @@ public class UtilityCommands extends MethodCommands { }); } - public static void list(File dir, Actor actor, CommandContext args, @Range(min = 0) int page, String formatName, boolean playerFolder, RunnableVal3 eachMsg) { + public static void list(File dir, Actor actor, CommandContext args, @Range(min = 0) int page, int perPage, String formatName, boolean playerFolder, RunnableVal3 eachMsg) { + AtomicInteger pageInt = new AtomicInteger(page); + List fileList = new ArrayList<>(); + page = getFiles(dir, actor, args, page, perPage, formatName, playerFolder, file -> fileList.add(file)); + + if (fileList.isEmpty()) { + BBC.SCHEMATIC_NONE.send(actor); + return; + } + + if (perPage == -1) perPage = actor instanceof Player ? 12 : 20; // More pages for console + int pageCount = (fileList.size() + perPage - 1) / perPage; + if (page < 1) { + BBC.SCHEMATIC_PAGE.send(actor, ">0"); + return; + } + if (page > pageCount) { + BBC.SCHEMATIC_PAGE.send(actor, "<" + (pageCount + 1)); + return; + } + + final int sortType = args.hasFlag('d') ? -1 : args.hasFlag('n') ? 1 : 0; + // cleanup file list + Collections.sort(fileList, new Comparator() { + @Override + public int compare(File f1, File f2) { + boolean dir1 = f1.isDirectory(); + boolean dir2 = f2.isDirectory(); + if (dir1 != dir2) return dir1 ? -1 : 1; + int res; + if (sortType == 0) { // use name by default + int p = f1.getParent().compareTo(f2.getParent()); + if (p == 0) { // same parent, compare names + res = f1.getName().compareTo(f2.getName()); + } else { // different parent, sort by that + res = p; + } + } else { + res = Long.valueOf(f1.lastModified()).compareTo(f2.lastModified()); // use date if there is a flag + if (sortType == 1) res = -res; // flip date for newest first instead of oldest first + } + return res; + } + }); + + int offset = (page - 1) * perPage; + + int limit = Math.min(offset + perPage, fileList.size()); + + String fullArgs = (String) args.getLocals().get("arguments"); + String baseCmd = null; + if (fullArgs != null) { + baseCmd = fullArgs.endsWith(" " + page) ? fullArgs.substring(0, fullArgs.length() - (" " + page).length()) : fullArgs; + } + Message m = new Message(BBC.SCHEMATIC_LIST, page, pageCount); + + UUID uuid = playerFolder ? actor.getUniqueId() : null; + for (int i = offset; i < limit; i++) { + m.newline(); + File file = fileList.get(i); + eachMsg.run(m, file.toURI(), getPath(dir, file, uuid)); + } + if (baseCmd != null) { + m.newline().paginate(baseCmd, page, pageCount); + } + m.send(actor); + } + + protected static int getFiles(File dir, Actor actor, CommandContext args, @Range(min = 0) int page, int perPage, String formatName, boolean playerFolder, Consumer forEachFile) { int len = args.argsLength(); List filters = new ArrayList<>(); @@ -696,90 +765,49 @@ public class UtilityCommands extends MethodCommands { return true; }; - List fileList = new ArrayList<>(); + if (formatName != null) { + final ClipboardFormat cf = ClipboardFormat.findByAlias(formatName); + forEachFile = new DelegateConsumer(forEachFile) { + @Override + public void accept(File file) { + if (cf.isFormat(file)) super.accept(file); + } + }; + } else { + forEachFile = new DelegateConsumer(forEachFile) { + @Override + public void accept(File file) { + if (!file.toString().endsWith(".cached")) super.accept(file); + } + }; + } + if (playerFolder) { if (listMine) { File playerDir = new File(dir, actor.getUniqueId() + dirFilter); - if (playerDir.exists()) fileList.addAll(allFiles(playerDir.listFiles(), false)); + if (playerDir.exists()) allFiles(playerDir.listFiles(), false, forEachFile); } if (listGlobal) { File rel = new File(dir, dirFilter); - if (rel.exists()) fileList.addAll(allFiles(rel.listFiles(ignoreUUIDs), false)); + forEachFile = new DelegateConsumer(forEachFile) { + @Override + public void accept(File f) { + try { + if (f.isDirectory()) { + UUID uuid = UUID.fromString(f.getName()); + return; + } + } catch (IllegalArgumentException exception) {} + super.accept(f); + } + }; + if (rel.exists()) allFiles(rel.listFiles(), false, forEachFile); } } else { File rel = new File(dir, dirFilter); - if (rel.exists()) fileList.addAll(allFiles(rel.listFiles(), false)); + if (rel.exists()) allFiles(rel.listFiles(), false, forEachFile); } - - if (fileList.isEmpty()) { - BBC.SCHEMATIC_NONE.send(actor); - return; - } - if (formatName != null) { - final ClipboardFormat cf = ClipboardFormat.findByAlias(formatName); - fileList = fileList.stream() - .filter(file -> cf.isFormat(file)) - .collect(Collectors.toList()); - - } - fileList = filter(fileList, filters); - - final int perPage = actor instanceof Player ? 12 : 20; // More pages for console - int pageCount = (fileList.size() + perPage - 1) / perPage; - if (page < 1) { - BBC.SCHEMATIC_PAGE.send(actor, ">0"); - return; - } - if (page > pageCount) { - BBC.SCHEMATIC_PAGE.send(actor, "<" + (pageCount + 1)); - return; - } - - final int sortType = args.hasFlag('d') ? -1 : args.hasFlag('n') ? 1 : 0; - // cleanup file list - Collections.sort(fileList, new Comparator() { - @Override - public int compare(File f1, File f2) { - boolean dir1 = f1.isDirectory(); - boolean dir2 = f2.isDirectory(); - if (dir1 != dir2) return dir1 ? -1 : 1; - int res; - if (sortType == 0) { // use name by default - int p = f1.getParent().compareTo(f2.getParent()); - if (p == 0) { // same parent, compare names - res = f1.getName().compareTo(f2.getName()); - } else { // different parent, sort by that - res = p; - } - } else { - res = Long.valueOf(f1.lastModified()).compareTo(f2.lastModified()); // use date if there is a flag - if (sortType == 1) res = -res; // flip date for newest first instead of oldest first - } - return res; - } - }); - - int offset = (page - 1) * perPage; - - int limit = Math.min(offset + perPage, fileList.size()); - - String fullArgs = (String) args.getLocals().get("arguments"); - String baseCmd = null; - if (fullArgs != null) { - baseCmd = fullArgs.endsWith(" " + page) ? fullArgs.substring(0, fullArgs.length() - (" " + page).length()) : fullArgs; - } - Message m = new Message(BBC.SCHEMATIC_LIST, page, pageCount); - - UUID uuid = playerFolder ? actor.getUniqueId() : null; - for (int i = offset; i < limit; i++) { - m.newline(); - File file = fileList.get(i); - eachMsg.run(m, file.toURI(), getPath(dir, file, uuid)); - } - if (baseCmd != null) { - m.newline().paginate(baseCmd, page, pageCount); - } - m.send(actor); + return page; } private static List filter(List fileList, List filters) { @@ -822,23 +850,19 @@ public class UtilityCommands extends MethodCommands { return fileList; } - private static List allFiles(File[] files, boolean recursive) { - if (files == null || files.length == 0) return Arrays.asList(); - List fileList = new ArrayList(); + private static void allFiles(File[] files, boolean recursive, Consumer task) { + if (files == null || files.length == 0) return; for (File f : files) { if (f.isDirectory()) { if (recursive) { - List subFiles = allFiles(f.listFiles(), recursive); - if (subFiles == null || subFiles.isEmpty()) continue; // empty subdir - fileList.addAll(subFiles); + allFiles(f.listFiles(), recursive, task); } else { - fileList.add(f); + task.accept(f); } } else { - fileList.add(f); + task.accept(f); } } - return fileList; } private static String getPath(File root, File file, UUID uuid) { 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 62e71e01..4d09df07 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 @@ -21,6 +21,7 @@ package com.sk89q.worldedit.extension.platform; import com.boydti.fawe.config.BBC; import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.object.brush.visualization.VirtualWorld; import com.boydti.fawe.object.exception.FaweException; import com.boydti.fawe.object.pattern.PatternTraverser; import com.boydti.fawe.util.MainUtil; @@ -328,10 +329,6 @@ public class PlatformManager { return tool; } - public void handleMove() { - - } - @SuppressWarnings("deprecation") @Subscribe public void handleBlockInteract(BlockInteractEvent event) { @@ -347,6 +344,13 @@ public class PlatformManager { if (actor instanceof Player) { final LocalSession session = worldEdit.getSessionManager().get(actor); Player playerActor = (Player) actor; + + VirtualWorld virtual = session.getVirtualWorld(); + if (virtual != null) { + virtual.handleBlockInteract(playerActor, vector, event); + if (event.isCancelled()) return; + } + if (event.getType() == Interaction.HIT) { if (session.isToolControlEnabled() && playerActor.getItemInHand() == getConfiguration().wandItem) { FawePlayer fp = FawePlayer.wrap(playerActor); @@ -394,6 +398,7 @@ public class PlatformManager { } }, true, true); event.setCancelled(true); + return; } } } else if (event.getType() == Interaction.OPEN) { @@ -402,17 +407,18 @@ public class PlatformManager { if (!actor.hasPermission("worldedit.selection.pos")) { return; } - final RegionSelector selector = session.getRegionSelector(playerActor.getWorld()); - final Player player = new LocationMaskedPlayerWrapper(PlayerWrapper.wrap((Player) actor), ((Player) actor).getLocation()); - fp.runAction(new Runnable() { - @Override - public void run() { - if (selector.selectSecondary(vector, ActorSelectorLimits.forActor(player))) { - selector.explainSecondarySelection(actor, session, vector); + if (fp.checkAction()) { + final RegionSelector selector = session.getRegionSelector(playerActor.getWorld()); + final Player player = new LocationMaskedPlayerWrapper(PlayerWrapper.wrap((Player) actor), ((Player) actor).getLocation()); + fp.runAction(new Runnable() { + @Override + public void run() { + if (selector.selectSecondary(vector, ActorSelectorLimits.forActor(player))) { + selector.explainSecondarySelection(actor, session, vector); + } } - } - }, true, true); - + }, true, true); + } event.setCancelled(true); return; } @@ -421,18 +427,21 @@ public class PlatformManager { if (tool != null && tool instanceof BlockTool) { if (tool.canUse(playerActor)) { FawePlayer fp = FawePlayer.wrap(playerActor); - final Player player = new LocationMaskedPlayerWrapper(PlayerWrapper.wrap((Player) actor), ((Player) actor).getLocation()); - fp.runAction(new Runnable() { - @Override - public void run() { - if (tool instanceof BrushTool) { - ((BlockTool) tool).actPrimary(queryCapability(Capability.WORLD_EDITING), getConfiguration(), player, session, location); - } else { - reset((BlockTool) tool).actPrimary(queryCapability(Capability.WORLD_EDITING), getConfiguration(), player, session, location); + if (fp.checkAction()) { + final Player player = new LocationMaskedPlayerWrapper(PlayerWrapper.wrap((Player) actor), ((Player) actor).getLocation()); + fp.runAction(new Runnable() { + @Override + public void run() { + if (tool instanceof BrushTool) { + ((BlockTool) tool).actPrimary(queryCapability(Capability.WORLD_EDITING), getConfiguration(), player, session, location); + } else { + reset((BlockTool) tool).actPrimary(queryCapability(Capability.WORLD_EDITING), getConfiguration(), player, session, location); + } } - } - }, true, true); - event.setCancelled(true); + }, true, true); + event.setCancelled(true); + return; + } } } } @@ -460,6 +469,13 @@ public class PlatformManager { // making changes to the world Player actor = createProxyActor(event.getPlayer()); final Player player = new LocationMaskedPlayerWrapper(PlayerWrapper.wrap(actor), actor.getLocation(), true); + final LocalSession session = worldEdit.getSessionManager().get(player); + + VirtualWorld virtual = session.getVirtualWorld(); + if (virtual != null) { + virtual.handlePlayerInput(player, event); + if (event.isCancelled()) return; + } try { switch (event.getInputType()) { @@ -484,8 +500,6 @@ public class PlatformManager { return; } - final LocalSession session = worldEdit.getSessionManager().get(player); - final Tool tool = session.getTool(player); if (tool != null && tool instanceof DoubleActionTraceTool) { if (tool.canUse(player)) { @@ -521,8 +535,6 @@ public class PlatformManager { return; } - final LocalSession session = worldEdit.getSessionManager().get(player); - final Tool tool = session.getTool(player); if (tool != null && tool instanceof TraceTool) { if (tool.canUse(player)) { diff --git a/core/src/main/java/com/sk89q/worldedit/extent/Extent.java b/core/src/main/java/com/sk89q/worldedit/extent/Extent.java index 9e051795..0e2e3411 100644 --- a/core/src/main/java/com/sk89q/worldedit/extent/Extent.java +++ b/core/src/main/java/com/sk89q/worldedit/extent/Extent.java @@ -38,7 +38,7 @@ public interface Extent extends InputExtent, OutputExtent { default @Nullable Entity createEntity(Location location, BaseEntity entity) { - throw new UnsupportedOperationException(getClass() + " does not support entity creation!"); + return null; } @Override @@ -240,7 +240,7 @@ public interface Extent extends InputExtent, OutputExtent { } } - default boolean contain(Vector pt) { + default boolean contains(Vector pt) { Vector min = getMinimumPoint(); Vector max = getMaximumPoint(); return (pt.containedWithin(min, max)); diff --git a/core/src/main/java/com/sk89q/worldedit/extent/clipboard/BlockArrayClipboard.java b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/BlockArrayClipboard.java index 65414839..22f3c4f2 100644 --- a/core/src/main/java/com/sk89q/worldedit/extent/clipboard/BlockArrayClipboard.java +++ b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/BlockArrayClipboard.java @@ -39,6 +39,7 @@ import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.world.biome.BaseBiome; import com.sk89q.worldedit.world.registry.BundledBlockData; +import java.io.Closeable; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -52,7 +53,7 @@ import static com.google.common.base.Preconditions.checkNotNull; * Stores block data as a multi-dimensional array of {@link BaseBlock}s and * other data as lists or maps. */ -public class BlockArrayClipboard implements Clipboard, LightingExtent { +public class BlockArrayClipboard implements Clipboard, LightingExtent, Closeable { private Region region; public FaweClipboard IMP; @@ -120,6 +121,7 @@ public class BlockArrayClipboard implements Clipboard, LightingExtent { close(); } + @Override public void close() { IMP.close(); } 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 fa3735ea..965c6fea 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 @@ -24,13 +24,7 @@ import com.boydti.fawe.config.Settings; import com.boydti.fawe.jnbt.NBTStreamer; import com.boydti.fawe.object.FaweOutputStream; import com.boydti.fawe.object.RunnableVal; -import com.boydti.fawe.object.clipboard.AbstractClipboardFormat; -import com.boydti.fawe.object.clipboard.DiskOptimizedClipboard; -import com.boydti.fawe.object.clipboard.FaweClipboard; -import com.boydti.fawe.object.clipboard.IClipboardFormat; -import com.boydti.fawe.object.clipboard.LazyClipboardHolder; -import com.boydti.fawe.object.clipboard.MultiClipboardHolder; -import com.boydti.fawe.object.clipboard.URIClipboardHolder; +import com.boydti.fawe.object.clipboard.*; import com.boydti.fawe.object.io.FastByteArrayOutputStream; import com.boydti.fawe.object.io.PGZIPOutputStream; import com.boydti.fawe.object.io.ResettableFileInputStream; @@ -47,32 +41,22 @@ import com.sk89q.jnbt.NBTConstants; import com.sk89q.jnbt.NBTInputStream; import com.sk89q.jnbt.NBTOutputStream; import com.sk89q.worldedit.LocalConfiguration; +import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; import com.sk89q.worldedit.extent.clipboard.Clipboard; +import com.sk89q.worldedit.session.ClipboardHolder; import com.sk89q.worldedit.world.registry.WorldData; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.DataInputStream; -import java.io.File; -import java.io.FileFilter; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; +import java.io.*; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; @@ -481,6 +465,38 @@ public enum ClipboardFormat { return format.getWriter(outputStream); } + /** + * Set the player's clipboard + * @param player + * @param uri + * @param in + * @return the held clipboard + * @throws IOException + */ + public ClipboardHolder hold(Player player, URI uri, InputStream in) throws IOException { + checkNotNull(player); + checkNotNull(uri); + checkNotNull(in); + + final ClipboardReader reader = getReader(in); + + final WorldData worldData = player.getWorld().getWorldData(); + final Clipboard clipboard; + + LocalSession session = WorldEdit.getInstance().getSessionManager().get(player); + session.setClipboard(null); + if (reader instanceof SchematicReader) { + clipboard = ((SchematicReader) reader).read(player.getWorld().getWorldData(), player.getUniqueId()); + } else if (reader instanceof StructureFormat) { + clipboard = ((StructureFormat) reader).read(player.getWorld().getWorldData(), player.getUniqueId()); + } else { + clipboard = reader.read(player.getWorld().getWorldData()); + } + URIClipboardHolder holder = new URIClipboardHolder(uri, clipboard, worldData); + session.setClipboard(holder); + return holder; + } + public Schematic load(File file) throws IOException { return load(new FileInputStream(file)); } diff --git a/forge112/src/main/java/com/boydti/fawe/forge/FaweForge.java b/forge112/src/main/java/com/boydti/fawe/forge/FaweForge.java index 12c5f268..9e0ad81e 100644 --- a/forge112/src/main/java/com/boydti/fawe/forge/FaweForge.java +++ b/forge112/src/main/java/com/boydti/fawe/forge/FaweForge.java @@ -68,7 +68,7 @@ public class FaweForge implements IFawe { @Override public String getPlatformVersion() { - return FMLCommonHandler.instance().getMinecraftServerInstance().getMinecraftVersion(); + return "1.12"; } @Override