+ * The only match currently attempted by this method is a retrieval by using a parameterless {@code getHandle()} method implemented by the runtime type of the specified object.
+ *
+ *
+ * @param obj The object for which to retrieve an NMS handle.
+ * @return The NMS handle of the specified object, or {@code null} if it could not be retrieved using {@code getHandle()}.
+ */
+ public synchronized static Object getHandle(Object obj) throws InvocationTargetException, IllegalAccessException, IllegalArgumentException {
+ return getMethod(obj.getClass(), "getHandle").invoke(obj);
+ }
+
+ /**
+ * Retrieves a {@link java.lang.reflect.Field} instance declared by the specified class with the specified name.
+ * Java access modifiers are ignored during this retrieval. No guarantee is made as to whether the field
+ * returned will be an instance or static field.
+ *
+ * A global caching mechanism within this class is used to store fields. Combined with synchronization, this guarantees that
+ * no field will be reflectively looked up twice.
+ *
+ *
+ * If a field is deemed suitable for return, {@link java.lang.reflect.Field#setAccessible(boolean) setAccessible} will be invoked with an argument of {@code true} before it is returned.
+ * This ensures that callers do not have to check or worry about Java access modifiers when dealing with the returned instance.
+ *
+ *
+ * @param clazz The class which contains the field to retrieve.
+ * @param name The declared name of the field in the class.
+ * @return A field object with the specified name declared by the specified class.
+ * @see Class#getDeclaredField(String)
+ */
+ public synchronized static Field getField(Class> clazz, String name) {
+ Map loaded;
+ if (!_loadedFields.containsKey(clazz)) {
+ loaded = new HashMap<>();
+ _loadedFields.put(clazz, loaded);
+ } else {
+ loaded = _loadedFields.get(clazz);
+ }
+ if (loaded.containsKey(name)) {
+ // If the field is loaded (or cached as not existing), return the relevant value, which might be null
+ return loaded.get(name);
+ }
+ try {
+ Field field = clazz.getDeclaredField(name);
+ field.setAccessible(true);
+ loaded.put(name, field);
+ return field;
+ } catch (NoSuchFieldException | SecurityException e) {
+ // Error loading
+ e.printStackTrace();
+ // Cache field as not existing
+ loaded.put(name, null);
+ return null;
+ }
+ }
+
+ /**
+ * Retrieves a {@link java.lang.reflect.Method} instance declared by the specified class with the specified name and argument types.
+ * Java access modifiers are ignored during this retrieval. No guarantee is made as to whether the field
+ * returned will be an instance or static field.
+ *
+ * A global caching mechanism within this class is used to store method. Combined with synchronization, this guarantees that
+ * no method will be reflectively looked up twice.
+ *
+ * If a method is deemed suitable for return, {@link java.lang.reflect.Method#setAccessible(boolean) setAccessible} will be invoked with an argument of {@code true} before it is returned.
+ * This ensures that callers do not have to check or worry about Java access modifiers when dealing with the returned instance.
+ *
+ * This method does not search superclasses of the specified type for methods with the specified signature.
+ * Callers wishing this behavior should use {@link Class#getDeclaredMethod(String, Class...)}.
+ *
+ * @param clazz The class which contains the method to retrieve.
+ * @param name The declared name of the method in the class.
+ * @param args The formal argument types of the method.
+ * @return A method object with the specified name declared by the specified class.
+ */
+ public synchronized static Method getMethod(Class> clazz, String name, Class>... args) {
+ if (!_loadedMethods.containsKey(clazz)) {
+ _loadedMethods.put(clazz, new HashMap>, Method>>());
+ }
+
+ Map>, Method>> loadedMethodNames = _loadedMethods.get(clazz);
+ if (!loadedMethodNames.containsKey(name)) {
+ loadedMethodNames.put(name, new HashMap>, Method>());
+ }
+
+ Map>, Method> loadedSignatures = loadedMethodNames.get(name);
+ ArrayWrapper> wrappedArg = new ArrayWrapper<>(args);
+ if (loadedSignatures.containsKey(wrappedArg)) {
+ return loadedSignatures.get(wrappedArg);
+ }
+
+ for (Method m : clazz.getMethods()) {
+ if (m.getName().equals(name) && Arrays.equals(args, m.getParameterTypes())) {
+ m.setAccessible(true);
+ loadedSignatures.put(wrappedArg, m);
+ return m;
+ }
+ }
+ loadedSignatures.put(wrappedArg, null);
+ return null;
+ }
+
+}
diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/chat/TextualComponent.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/chat/TextualComponent.java
new file mode 100644
index 00000000..68a9ed0a
--- /dev/null
+++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/chat/TextualComponent.java
@@ -0,0 +1,316 @@
+package com.boydti.fawe.bukkit.chat;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import org.bukkit.configuration.serialization.ConfigurationSerializable;
+import org.bukkit.configuration.serialization.ConfigurationSerialization;
+
+/**
+ * Represents a textual component of a message part.
+ * This can be used to not only represent string literals in a JSON message,
+ * but also to represent localized strings and other text values.
+ * Different instances of this class can be created with static constructor methods.
+ */
+public abstract class TextualComponent implements Cloneable {
+
+ static {
+ ConfigurationSerialization.registerClass(TextualComponent.ArbitraryTextTypeComponent.class);
+ ConfigurationSerialization.registerClass(TextualComponent.ComplexTextTypeComponent.class);
+ }
+
+ static TextualComponent deserialize(Map map) {
+ if (map.containsKey("key") && map.size() == 2 && map.containsKey("value")) {
+ // Arbitrary text component
+ return ArbitraryTextTypeComponent.deserialize(map);
+ } else if (map.size() >= 2 && map.containsKey("key") && !map.containsKey("value") /* It contains keys that START WITH value */) {
+ // Complex JSON object
+ return ComplexTextTypeComponent.deserialize(map);
+ }
+
+ return null;
+ }
+
+ static boolean isTextKey(String key) {
+ return key.equals("translate") || key.equals("text") || key.equals("score") || key.equals("selector");
+ }
+
+ static boolean isTranslatableText(TextualComponent component) {
+ return component instanceof ComplexTextTypeComponent && component.getKey().equals("translate");
+ }
+
+ /**
+ * Create a textual component representing a string literal.
+ *
+ * This is the default type of textual component when a single string
+ * literal is given to a method.
+ *
+ * @param textValue The text which will be represented.
+ * @return The text component representing the specified literal text.
+ */
+ public static TextualComponent rawText(String textValue) {
+ return new ArbitraryTextTypeComponent("text", textValue);
+ }
+
+ /**
+ * Create a textual component representing a localized string.
+ * The client will see this text component as their localized version of the specified string key , which can be overridden by a
+ * resource pack.
+ *
+ * If the specified translation key is not present on the client resource pack, the translation key will be displayed as a string literal to
+ * the client.
+ *
+ *
+ * @param translateKey The string key which maps to localized text.
+ * @return The text component representing the specified localized text.
+ */
+ public static TextualComponent localizedText(String translateKey) {
+ return new ArbitraryTextTypeComponent("translate", translateKey);
+ }
+
+ private static void throwUnsupportedSnapshot() {
+ throw new UnsupportedOperationException("This feature is only supported in snapshot releases.");
+ }
+
+ /**
+ * Create a textual component representing a scoreboard value.
+ * The client will see their own score for the specified objective as the text represented by this component.
+ *
+ * This method is currently guaranteed to throw an {@code UnsupportedOperationException} as it is only supported on snapshot clients.
+ *
+ *
+ * @param scoreboardObjective The name of the objective for which to display the score.
+ * @return The text component representing the specified scoreboard score (for the viewing player), or {@code null} if an error occurs during
+ * JSON serialization.
+ */
+ public static TextualComponent objectiveScore(String scoreboardObjective) {
+ return objectiveScore("*", scoreboardObjective);
+ }
+
+ /**
+ * Create a textual component representing a scoreboard value.
+ * The client will see the score of the specified player for the specified objective as the text represented by this component.
+ *
+ * This method is currently guaranteed to throw an {@code UnsupportedOperationException}
+ * as it is only supported on snapshot clients.
+ *
+ * @param playerName The name of the player whos score will be shown. If
+ * this string represents the single-character sequence
+ * "*", the viewing player's score will be displayed.
+ * Standard minecraft selectors (@a, @p, etc)
+ * are not supported.
+ * @param scoreboardObjective The name of the objective for
+ * which to display the score.
+ * @return The text component representing the specified scoreboard score
+ * for the specified player, or {@code null} if an error occurs during JSON serialization.
+ */
+ public static TextualComponent objectiveScore(String playerName, String scoreboardObjective) {
+ throwUnsupportedSnapshot(); // Remove this line when the feature is released to non-snapshot versions, in addition to updating ALL THE
+ // OVERLOADS documentation accordingly
+
+ return new ComplexTextTypeComponent("score", ImmutableMap.builder()
+ .put("name", playerName)
+ .put("objective", scoreboardObjective)
+ .build());
+ }
+
+ /**
+ * Create a textual component representing a player name, retrievable by using a standard minecraft selector.
+ * The client will see the players or entities captured by the specified selector as the text represented by this component.
+ *
+ * This method is currently guaranteed to throw an {@code UnsupportedOperationException} as it is only supported on snapshot clients.
+ *
+ *
+ * @param selector The minecraft player or entity selector which will capture the entities whose string representations will be displayed in
+ * the place of this text component.
+ * @return The text component representing the name of the entities captured by the selector.
+ */
+ public static TextualComponent selector(String selector) {
+ throwUnsupportedSnapshot(); // Remove this line when the feature is released to non-snapshot versions, in addition to updating ALL THE
+ // OVERLOADS documentation accordingly
+
+ return new ArbitraryTextTypeComponent("selector", selector);
+ }
+
+ @Override
+ public String toString() {
+ return getReadableString();
+ }
+
+ /**
+ * @return The JSON key used to represent text components of this type.
+ */
+ public abstract String getKey();
+
+ /**
+ * @return A readable String
+ */
+ public abstract String getReadableString();
+
+ /**
+ * Clones a textual component instance.
+ * The returned object should not reference this textual component instance, but should maintain the same key and value.
+ */
+ @Override
+ public abstract TextualComponent clone() throws CloneNotSupportedException;
+
+ /**
+ * Writes the text data represented by this textual component to the specified JSON writer object.
+ * A new object within the writer is not started.
+ *
+ * @param writer The object to which to write the JSON data.
+ * @throws java.io.IOException If an error occurs while writing to the stream.
+ */
+ public abstract void writeJson(JsonWriter writer) throws IOException;
+
+ /**
+ * Internal class used to represent all types of text components.
+ * Exception validating done is on keys and values.
+ */
+ private static final class ArbitraryTextTypeComponent extends TextualComponent implements ConfigurationSerializable {
+
+ private String key;
+ private String value;
+
+ public ArbitraryTextTypeComponent(String key, String value) {
+ setKey(key);
+ setValue(value);
+ }
+
+ public static ArbitraryTextTypeComponent deserialize(Map map) {
+ return new ArbitraryTextTypeComponent(map.get("key").toString(), map.get("value").toString());
+ }
+
+ @Override
+ public String getKey() {
+ return key;
+ }
+
+ public void setKey(String key) {
+ Preconditions.checkArgument(key != null && !key.isEmpty(), "The key must be specified.");
+ this.key = key;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ Preconditions.checkArgument(value != null, "The value must be specified.");
+ this.value = value;
+ }
+
+ @Override
+ public TextualComponent clone() throws CloneNotSupportedException {
+ // Since this is a private and final class, we can just reinstantiate this class instead of casting super.clone
+ return new ArbitraryTextTypeComponent(getKey(), getValue());
+ }
+
+ @Override
+ public void writeJson(JsonWriter writer) throws IOException {
+ writer.name(getKey()).value(getValue());
+ }
+
+ @Override
+ @SuppressWarnings("serial")
+ public Map serialize() {
+ return new HashMap() {
+ {
+ put("key", getKey());
+ put("value", getValue());
+ }
+ };
+ }
+
+ @Override
+ public String getReadableString() {
+ return getValue();
+ }
+ }
+
+ /**
+ * Internal class used to represent a text component with a nested JSON
+ * value.
+ *
+ * Exception validating done is on keys and values.
+ */
+ private static final class ComplexTextTypeComponent extends TextualComponent implements ConfigurationSerializable {
+
+ private String key;
+ private Map value;
+
+ public ComplexTextTypeComponent(String key, Map values) {
+ setKey(key);
+ setValue(values);
+ }
+
+ public static ComplexTextTypeComponent deserialize(Map map) {
+ String key = null;
+ Map value = new HashMap<>();
+ for (Map.Entry valEntry : map.entrySet()) {
+ if (valEntry.getKey().equals("key")) {
+ key = (String) valEntry.getValue();
+ } else if (valEntry.getKey().startsWith("value.")) {
+ value.put(valEntry.getKey().substring(6) /* Strips out the value prefix */, valEntry.getValue().toString());
+ }
+ }
+ return new ComplexTextTypeComponent(key, value);
+ }
+
+ @Override
+ public String getKey() {
+ return key;
+ }
+
+ public void setKey(String key) {
+ Preconditions.checkArgument(key != null && !key.isEmpty(), "The key must be specified.");
+ this.key = key;
+ }
+
+ public Map getValue() {
+ return value;
+ }
+
+ public void setValue(Map value) {
+ Preconditions.checkArgument(value != null, "The value must be specified.");
+ this.value = value;
+ }
+
+ @Override
+ public TextualComponent clone() {
+ // Since this is a private and final class, we can just reinstantiate this class instead of casting super.clone
+ return new ComplexTextTypeComponent(getKey(), getValue());
+ }
+
+ @Override
+ public void writeJson(JsonWriter writer) throws IOException {
+ writer.name(getKey());
+ writer.beginObject();
+ for (Map.Entry jsonPair : value.entrySet()) {
+ writer.name(jsonPair.getKey()).value(jsonPair.getValue());
+ }
+ writer.endObject();
+ }
+
+ @Override
+ @SuppressWarnings("serial")
+ public Map serialize() {
+ return new HashMap() {
+ {
+ put("key", getKey());
+ for (Entry valEntry : getValue().entrySet()) {
+ put("value." + valEntry.getKey(), valEntry.getValue());
+ }
+ }
+ };
+ }
+
+ @Override
+ public String getReadableString() {
+ return getKey();
+ }
+ }
+}
diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitQueue_0.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitQueue_0.java
index d146ad5a..8ae633d4 100644
--- a/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitQueue_0.java
+++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitQueue_0.java
@@ -4,7 +4,6 @@ import com.boydti.fawe.Fawe;
import com.boydti.fawe.FaweCache;
import com.boydti.fawe.bukkit.BukkitPlayer;
import com.boydti.fawe.bukkit.FaweBukkit;
-import com.boydti.fawe.config.Settings;
import com.boydti.fawe.example.CharFaweChunk;
import com.boydti.fawe.example.NMSMappedFaweQueue;
import com.boydti.fawe.object.FaweChunk;
@@ -217,7 +216,6 @@ public abstract class BukkitQueue_0 extends NMSMa
private volatile boolean timingsEnabled;
private static boolean alertTimingsChange = true;
- private volatile int parallelThreads;
private static Field fieldTimingsEnabled;
private static Field fieldAsyncCatcherEnabled;
@@ -239,10 +237,6 @@ public abstract class BukkitQueue_0 extends NMSMa
public void startSet(boolean parallel) {
ChunkListener.physicsFreeze = true;
if (parallel) {
- if (Fawe.get().isMainThread()) {
- parallelThreads = Settings.IMP.QUEUE.PARALLEL_THREADS;
- Settings.IMP.QUEUE.PARALLEL_THREADS = 1;
- }
try {
if (fieldAsyncCatcherEnabled != null) {
fieldAsyncCatcherEnabled.set(null, false);
@@ -268,9 +262,6 @@ public abstract class BukkitQueue_0 extends NMSMa
public void endSet(boolean parallel) {
ChunkListener.physicsFreeze = false;
if (parallel) {
- if (Fawe.get().isMainThread() && parallelThreads != 0) {
- Settings.IMP.QUEUE.PARALLEL_THREADS = parallelThreads;
- }
try {
if (fieldAsyncCatcherEnabled != null) {
fieldAsyncCatcherEnabled.set(null, true);
@@ -283,7 +274,6 @@ public abstract class BukkitQueue_0 extends NMSMa
e.printStackTrace();
}
}
- parallelThreads = 0;
}
@Override
diff --git a/bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitPlayerBlockBag.java b/bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitPlayerBlockBag.java
new file mode 100644
index 00000000..aa8d64b4
--- /dev/null
+++ b/bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitPlayerBlockBag.java
@@ -0,0 +1,204 @@
+/*
+ * WorldEdit, a Minecraft world manipulation toolkit
+ * Copyright (C) sk89q
+ * Copyright (C) WorldEdit team and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.bukkit;
+
+import com.sk89q.worldedit.WorldVector;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+import com.sk89q.worldedit.extent.inventory.*;
+import com.sk89q.worldedit.blocks.BaseItem;
+import com.sk89q.worldedit.blocks.BaseItemStack;
+import com.sk89q.worldedit.blocks.BlockID;
+import com.sk89q.worldedit.blocks.ItemType;
+
+public class BukkitPlayerBlockBag extends BlockBag {
+
+ private Player player;
+ private ItemStack[] items;
+
+ /**
+ * Construct the object.
+ *
+ * @param player the player
+ */
+ public BukkitPlayerBlockBag(Player player) {
+ this.player = player;
+ }
+
+ /**
+ * Loads inventory on first use.
+ */
+ private void loadInventory() {
+ if (items == null) {
+ items = player.getInventory().getContents();
+ }
+ }
+
+ /**
+ * Get the player.
+ *
+ * @return the player
+ */
+ public Player getPlayer() {
+ return player;
+ }
+
+ @Override
+ public void fetchItem(BaseItem item) throws BlockBagException {
+ final int id = item.getType();
+ final int damage = item.getData();
+ int amount = (item instanceof BaseItemStack) ? ((BaseItemStack) item).getAmount() : 1;
+ assert(amount == 1);
+ boolean usesDamageValue = ItemType.usesDamageValue(id);
+
+ if (id == BlockID.AIR) {
+ throw new IllegalArgumentException("Can't fetch air block");
+ }
+
+ loadInventory();
+
+ boolean found = false;
+
+ for (int slot = 0; slot < items.length; ++slot) {
+ ItemStack bukkitItem = items[slot];
+
+ if (bukkitItem == null) {
+ continue;
+ }
+
+ if (bukkitItem.getTypeId() != id) {
+ // Type id doesn't fit
+ continue;
+ }
+
+ if (usesDamageValue && bukkitItem.getDurability() != damage) {
+ // Damage value doesn't fit.
+ continue;
+ }
+
+ int currentAmount = bukkitItem.getAmount();
+ if (currentAmount < 0) {
+ // Unlimited
+ return;
+ }
+
+ if (currentAmount > 1) {
+ bukkitItem.setAmount(currentAmount - 1);
+ found = true;
+ } else {
+ items[slot] = null;
+ found = true;
+ }
+
+ break;
+ }
+
+ if (!found) {
+ throw new OutOfBlocksException();
+ }
+ }
+
+ @Override
+ public void storeItem(BaseItem item) throws BlockBagException {
+ final int id = item.getType();
+ final int damage = item.getData();
+ int amount = (item instanceof BaseItemStack) ? ((BaseItemStack) item).getAmount() : 1;
+ assert(amount <= 64);
+ boolean usesDamageValue = ItemType.usesDamageValue(id);
+
+ if (id == BlockID.AIR) {
+ throw new IllegalArgumentException("Can't store air block");
+ }
+
+ loadInventory();
+
+ int freeSlot = -1;
+
+ for (int slot = 0; slot < items.length; ++slot) {
+ ItemStack bukkitItem = items[slot];
+
+ if (bukkitItem == null) {
+ // Delay using up a free slot until we know there are no stacks
+ // of this item to merge into
+
+ if (freeSlot == -1) {
+ freeSlot = slot;
+ }
+ continue;
+ }
+
+ if (bukkitItem.getTypeId() != id) {
+ // Type id doesn't fit
+ continue;
+ }
+
+ if (usesDamageValue && bukkitItem.getDurability() != damage) {
+ // Damage value doesn't fit.
+ continue;
+ }
+
+ int currentAmount = bukkitItem.getAmount();
+ if (currentAmount < 0) {
+ // Unlimited
+ return;
+ }
+ if (currentAmount >= 64) {
+ // Full stack
+ continue;
+ }
+
+ int spaceLeft = 64 - currentAmount;
+ if (spaceLeft >= amount) {
+ bukkitItem.setAmount(currentAmount + amount);
+ return;
+ }
+
+ bukkitItem.setAmount(64);
+ amount -= spaceLeft;
+ }
+
+ if (freeSlot > -1) {
+ items[freeSlot] = new ItemStack(id, amount);
+ return;
+ }
+
+ throw new OutOfSpaceException(id);
+ }
+
+ @Override
+ public void flushChanges() {
+ if (items != null) {
+ player.getInventory().setContents(items);
+ items = null;
+ }
+ }
+
+ @Override
+ public void addSourcePosition(WorldVector pos) {
+ }
+
+ @Override
+ public void addSingleSourcePosition(WorldVector pos) {
+ }
+
+ public static Class> inject() {
+ return BukkitPlayerBlockBag.class;
+ }
+}
\ No newline at end of file
diff --git a/core/build.gradle b/core/build.gradle
index 51ead323..50f6e5f2 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -7,7 +7,7 @@ dependencies {
compile 'com.google.code.gson:gson:2.2.4'
compile 'net.fabiozumbi12:redprotect:1.9.6'
compile 'com.sk89q:worldguard:6.0.0-SNAPSHOT'
- compile group: "com.plotsquared", name: "plotsquared-api", version: "latest", changing: true
+ compile group: "com.plotsquared", name: "plotsquared-api", version: "latest"
compile 'org.primesoft:BlocksHub:2.0'
compile 'com.github.luben:zstd-jni:1.1.1'
// compile 'org.javassist:javassist:3.22.0-CR1'
diff --git a/core/src/main/java/com/boydti/fawe/Fawe.java b/core/src/main/java/com/boydti/fawe/Fawe.java
index e0c894ab..1e053545 100644
--- a/core/src/main/java/com/boydti/fawe/Fawe.java
+++ b/core/src/main/java/com/boydti/fawe/Fawe.java
@@ -13,11 +13,12 @@ import com.boydti.fawe.util.FaweTimer;
import com.boydti.fawe.util.MainUtil;
import com.boydti.fawe.util.MemUtil;
import com.boydti.fawe.util.RandomTextureUtil;
-import com.boydti.fawe.util.StringMan;
import com.boydti.fawe.util.TaskManager;
import com.boydti.fawe.util.TextureUtil;
import com.boydti.fawe.util.Updater;
import com.boydti.fawe.util.WEManager;
+import com.boydti.fawe.util.chat.ChatManager;
+import com.boydti.fawe.util.chat.PlainChatManager;
import com.sk89q.jnbt.NBTInputStream;
import com.sk89q.jnbt.NBTOutputStream;
import com.sk89q.worldedit.BlockVector;
@@ -122,6 +123,7 @@ import com.sk89q.worldedit.session.ClipboardHolder;
import com.sk89q.worldedit.session.PasteBuilder;
import com.sk89q.worldedit.session.SessionManager;
import com.sk89q.worldedit.session.request.Request;
+import com.sk89q.worldedit.util.command.SimpleCommandMapping;
import com.sk89q.worldedit.util.command.SimpleDispatcher;
import com.sk89q.worldedit.util.command.fluent.DispatcherNode;
import com.sk89q.worldedit.util.command.parametric.ParameterData;
@@ -153,6 +155,9 @@ import javax.management.Notification;
import javax.management.NotificationEmitter;
import javax.management.NotificationListener;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
/**
* [ WorldEdit action]
* |
@@ -205,6 +210,7 @@ public class Fawe {
private Updater updater;
private TextureUtil textures;
private DefaultTransformParser transformParser;
+ private ChatManager chatManager = new PlainChatManager();
// @Deprecated
// private boolean isJava8 = MainUtil.getJavaVersion() >= 1.8;
@@ -246,7 +252,7 @@ public class Fawe {
public static void debugPlain(String s) {
if (INSTANCE != null) {
- INSTANCE.IMP.debug(StringMan.getString(s));
+ INSTANCE.IMP.debug(s);
} else {
System.out.println(s);
}
@@ -333,7 +339,16 @@ public class Fawe {
return false;
}
-// @Deprecated
+ public ChatManager getChatManager() {
+ return chatManager;
+ }
+
+ public void setChatManager(ChatManager chatManager) {
+ checkNotNull(chatManager);
+ this.chatManager = chatManager;
+ }
+
+ // @Deprecated
// public boolean isJava8() {
// return isJava8;
// }
@@ -601,6 +616,7 @@ public class Fawe {
CommandManager.inject(); // Async commands
PlatformManager.inject(); // Async brushes / tools
SimpleDispatcher.inject(); // Optimize perm checks
+ SimpleCommandMapping.inject(); // Hashcode + equals
} catch (Throwable e) {
debug("====== UPDATE WORLDEDIT TO 6.1.1 ======");
MainUtil.handleError(e, false);
@@ -618,7 +634,7 @@ public class Fawe {
debug(" - AsyncWorldEdit/WorldEditRegions isn't installed");
debug(" - Any other errors in the startup log");
debug("Contact Empire92 if you need assistance:");
- debug(" - Send me a PM or ask on IRC");
+ debug(" - Send me a PM or ask on IRC/Discord");
debug(" - http://webchat.esper.net/?nick=&channels=IntellectualCrafters");
debug("=======================================");
}
diff --git a/core/src/main/java/com/boydti/fawe/command/AnvilCommands.java b/core/src/main/java/com/boydti/fawe/command/AnvilCommands.java
index 1d562dac..7449a326 100644
--- a/core/src/main/java/com/boydti/fawe/command/AnvilCommands.java
+++ b/core/src/main/java/com/boydti/fawe/command/AnvilCommands.java
@@ -15,6 +15,7 @@ import com.boydti.fawe.jnbt.anvil.filters.DeleteOldFilter;
import com.boydti.fawe.jnbt.anvil.filters.DeleteUninhabitedFilter;
import com.boydti.fawe.jnbt.anvil.filters.MappedReplacePatternFilter;
import com.boydti.fawe.jnbt.anvil.filters.PlotTrimFilter;
+import com.boydti.fawe.jnbt.anvil.filters.RemapFilter;
import com.boydti.fawe.jnbt.anvil.filters.RemoveLayerFilter;
import com.boydti.fawe.jnbt.anvil.filters.ReplacePatternFilter;
import com.boydti.fawe.jnbt.anvil.filters.ReplaceSimpleFilter;
@@ -22,6 +23,7 @@ import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.object.FaweQueue;
import com.boydti.fawe.object.RegionWrapper;
import com.boydti.fawe.object.RunnableVal4;
+import com.boydti.fawe.object.clipboard.ClipboardRemapper;
import com.boydti.fawe.object.mask.FaweBlockMatcher;
import com.boydti.fawe.util.MainUtil;
import com.boydti.fawe.util.SetQueue;
@@ -80,6 +82,7 @@ public class AnvilCommands {
* @param
* @return
*/
+ @Deprecated
public static > T runWithWorld(Player player, String folder, T filter, boolean force) {
boolean copy = false;
if (FaweAPI.getWorld(folder) != null) {
@@ -109,6 +112,7 @@ public class AnvilCommands {
* @param
* @return
*/
+ @Deprecated
public static > T runWithSelection(Player player, EditSession editSession, Region selection, T filter) {
if (!(selection instanceof CuboidRegion)) {
BBC.NO_REGION.send(player);
@@ -159,10 +163,37 @@ public class AnvilCommands {
if (result != null) player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(result.getTotal()));
}
+ @Command(
+ aliases = {"remapall"},
+ usage = "",
+ help = "Remap the world between MCPE/PC values",
+ desc = "Remap the world between MCPE/PC values",
+ min = 1,
+ max = 1
+ )
+ @CommandPermissions("worldedit.anvil.remapall")
+ public void remapall(Player player, String folder, @Switch('f') boolean force) throws WorldEditException {
+ ClipboardRemapper mapper;
+ ClipboardRemapper.RemapPlatform from;
+ ClipboardRemapper.RemapPlatform to;
+ if (Fawe.imp().getPlatform().equalsIgnoreCase("nukkit")) {
+ from = ClipboardRemapper.RemapPlatform.PC;
+ to = ClipboardRemapper.RemapPlatform.PE;
+ } else {
+ from = ClipboardRemapper.RemapPlatform.PE;
+ to = ClipboardRemapper.RemapPlatform.PC;
+ }
+ RemapFilter filter = new RemapFilter(from, to);
+ RemapFilter result = runWithWorld(player, folder, filter, force);
+ if (result != null) player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(result.getTotal()));
+ }
+
+
@Command(
aliases = {"deleteallunvisited", "delunvisited" },
usage = " [file-age=60000]",
- desc = "Delete all chunks which haven't been occupied for `age-ticks` (20t = 1s) and \n" +
+ desc = "Delete all chunks which haven't been occupied",
+ help = "Delete all chunks which haven't been occupied for `age-ticks` (20t = 1s) and \n" +
"Have not been accessed since `file-duration` (ms) after creation and\n" +
"Have not been used in the past `chunk-inactivity` (ms)" +
"The auto-save interval is the recommended value for `file-duration` and `chunk-inactivity`",
@@ -180,7 +211,8 @@ public class AnvilCommands {
@Command(
aliases = {"deletealloldregions", "deloldreg" },
usage = " ",
- desc = "Delete regions which haven't been accessed in a certain amount of time\n" +
+ desc = "Delete regions which haven't been accessed in a certain amount of time",
+ help = "Delete regions which haven't been accessed in a certain amount of time\n" +
"You can use seconds (s), minutes (m), hours (h), days (d), weeks (w), years (y)\n" +
"(months are not a unit of time)\n" +
"E.g. 8h5m12s\n",
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 3165f4a2..66957025 100644
--- a/core/src/main/java/com/boydti/fawe/config/BBC.java
+++ b/core/src/main/java/com/boydti/fawe/config/BBC.java
@@ -8,12 +8,18 @@ import com.boydti.fawe.object.PseudoRandom;
import com.boydti.fawe.object.RunnableVal3;
import com.boydti.fawe.util.MainUtil;
import com.boydti.fawe.util.StringMan;
+import com.boydti.fawe.util.chat.Message;
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
import java.util.Set;
public enum BBC {
@@ -177,7 +183,6 @@ public enum BBC {
NOTHING_CONFIRMED("You have no actions pending confirmation.", "WorldEdit.Utility"),
- SCHEMATIC_DELETE("%s0 has been deleted.", "Worldedit.Schematic"),
SCHEMATIC_FORMAT("Available formats (Name: Lookup names)", "Worldedit.Schematic"),
SCHEMATIC_LOADED("%s0 loaded. Paste it with //paste", "Worldedit.Schematic"),
SCHEMATIC_SAVED("%s0 saved.", "Worldedit.Schematic"),
@@ -214,7 +219,8 @@ public enum BBC {
HELP_ITEM_ALLOWED("&a%s0&8 - &7%s1", "WorldEdit.Help"),
HELP_ITEM_DENIED("&c%s0&8 - &7%s1", "WorldEdit.Help"),
HELP_HEADER("Help: page %s0/%s1", "WorldEdit.Help"),
- HELP_HEADER_FOOTER("&7Use: &8//help [type|command|search] [#]&7\n&7Wiki: https://git.io/vSKE5", "WorldEdit.Help"),
+ HELP_FOOTER("&7Wiki: https://git.io/vSKE5", "WorldEdit.Help"),
+ PAGE_FOOTER("Use %s0 to go to the next page", "WorldEdit.Utility"),
PROGRESS_MESSAGE("%s1/%s0 (%s2%) @%s3cps %s4s left", "Progress"),
PROGRESS_FINISHED("[ Done! ]", "Progress"),
@@ -318,6 +324,14 @@ public enum BBC {
private static final HashMap replacements = new HashMap<>();
+ static {
+ for (final char letter : "1234567890abcdefklmnor".toCharArray()) {
+ replacements.put("&" + letter, "\u00a7" + letter);
+ }
+ replacements.put("\\\\n", "\n");
+ replacements.put("\\n", "\n");
+ replacements.put("&-", "\n");
+ }
/**
* Translated
*/
@@ -428,13 +442,6 @@ public enum BBC {
changed = true;
yml.set(remove, null);
}
- replacements.clear();
- for (final char letter : "1234567890abcdefklmnor".toCharArray()) {
- replacements.put("&" + letter, "\u00a7" + letter);
- }
- replacements.put("\\\\n", "\n");
- replacements.put("\\n", "\n");
- replacements.put("&-", "\n");
for (final BBC caption : all) {
if (!captions.contains(caption)) {
changed = true;
@@ -467,10 +474,19 @@ public enum BBC {
return StringMan.replaceFromMap(string, replacements);
}
+ public static String stripColor(String string) {
+
+ return StringMan.removeFromSet(string, replacements.keySet());
+ }
+
public String s() {
return this.s;
}
+ public Message m(Object... args) {
+ return new Message(this, args);
+ }
+
public String original() {
return d;
}
@@ -524,6 +540,56 @@ public enum BBC {
}
}
+ public static char getCode(String name) {
+ switch (name) {
+ case "BLACK":
+ return '0';
+ case "DARK_BLUE":
+ return '1';
+ case "DARK_GREEN":
+ return '2';
+ case "DARK_AQUA":
+ return '3';
+ case "DARK_RED":
+ return '4';
+ case "DARK_PURPLE":
+ return '5';
+ case "GOLD":
+ return '6';
+ case "GRAY":
+ return '7';
+ case "DARK_GRAY":
+ return '8';
+ case "BLUE":
+ return '9';
+ case "GREEN":
+ return 'a';
+ case "AQUA":
+ return 'b';
+ case "RED":
+ return 'c';
+ case "LIGHT_PURPLE":
+ return 'd';
+ case "YELLOW":
+ return 'e';
+ case "WHITE":
+ return 'f';
+ case "OBFUSCATED":
+ return 'k';
+ case "BOLD":
+ return 'l';
+ case "STRIKETHROUGH":
+ return 'm';
+ case "UNDERLINE":
+ return 'n';
+ case "ITALIC":
+ return 'o';
+ default:
+ case "RESET":
+ return 'r';
+ }
+ }
+
public static String getColorName(char code) {
switch (code) {
case '0':
@@ -574,6 +640,68 @@ public enum BBC {
}
}
+ private static Object[] append(StringBuilder builder, Map obj, String color, Map properties) {
+ Object[] style = new Object[] { color, properties };
+ for (Map.Entry entry : obj.entrySet()) {
+ switch (entry.getKey()) {
+ case "text":
+ String text = (String) entry.getValue();
+ String newColor = (String) obj.get("color");
+ String newBold = (String) obj.get("bold");
+ int index = builder.length();
+ if (!Objects.equals(color, newColor)) {
+ style[0] = newColor;
+ char code = BBC.getCode(newColor.toUpperCase());
+ builder.append('\u00A7').append(code);
+ }
+ for (Map.Entry entry2 : obj.entrySet()) {
+ if (StringMan.isEqualIgnoreCaseToAny(entry2.getKey(), "bold", "italic", "underlined", "strikethrough", "obfuscated")) {
+ boolean newValue = Boolean.valueOf((String) entry2.getValue());
+ if (properties.put(entry2.getKey(), newValue) != newValue) {
+ if (newValue) {
+ char code = BBC.getCode(entry2.getKey().toUpperCase());
+ builder.append('\u00A7').append(code);
+ } else {
+ builder.insert(index, '\u00A7').append('r');
+ if (Objects.equals(color, newColor) && newColor != null) {
+ builder.append('\u00A7').append(BBC.getCode(newColor.toUpperCase()));
+ }
+ }
+ }
+ }
+ }
+ builder.append(text);
+ break;
+ case "extra":
+ List> list = (List>) entry.getValue();
+ for (Map elem : list) {
+ elem.putIfAbsent("color", obj.get("color"));
+ for (Map.Entry entry2 : obj.entrySet()) {
+ if (StringMan.isEqualIgnoreCaseToAny(entry2.getKey(), "bold", "italic", "underlined", "strikethrough", "obfuscated")) {
+ elem.putIfAbsent(entry2.getKey(), entry2.getValue());
+ }
+ }
+ style = append(builder, elem, (String) style[0], (Map) style[1]);
+ }
+ }
+ }
+ return style;
+ }
+
+ public static String jsonToString(String text) {
+ Gson gson = new Gson();
+ StringBuilder builder = new StringBuilder();
+ Map obj = gson.fromJson(text, new TypeToken>() {}.getType());
+ HashMap properties = new HashMap<>();
+ properties.put("bold", false);
+ properties.put("italic", false);
+ properties.put("underlined", false);
+ properties.put("strikethrough", false);
+ properties.put("obfuscated", false);
+ append(builder, obj, null, properties);
+ return builder.toString();
+ }
+
/**
* @param m
* @param runPart Part, Color, NewLine
diff --git a/core/src/main/java/com/boydti/fawe/config/Commands.java b/core/src/main/java/com/boydti/fawe/config/Commands.java
index 05260916..c2187733 100644
--- a/core/src/main/java/com/boydti/fawe/config/Commands.java
+++ b/core/src/main/java/com/boydti/fawe/config/Commands.java
@@ -37,12 +37,14 @@ public class Commands {
return new TranslatedCommand(clazz.getSimpleName(), command);
}
- public static String getAlias(String command) {
+ public static String getAlias(Class clazz, String command) {
if (cmdConfig == null) {
return command;
}
- ConfigurationSection commands = cmdConfig.getConfigurationSection(command);
- List aliases = commands.getStringList("aliases");
+ List aliases = cmdConfig.getStringList(clazz + "." + command + ".aliases");
+ if (aliases == null) {
+ aliases = cmdConfig.getStringList(command + ".aliases");
+ }
return (aliases == null || aliases.isEmpty()) ? command : aliases.get(0);
}
diff --git a/core/src/main/java/com/boydti/fawe/database/DBHandler.java b/core/src/main/java/com/boydti/fawe/database/DBHandler.java
index 4c0ee486..a9d47e9e 100644
--- a/core/src/main/java/com/boydti/fawe/database/DBHandler.java
+++ b/core/src/main/java/com/boydti/fawe/database/DBHandler.java
@@ -21,7 +21,11 @@ public class DBHandler {
databases.put(worldName, database);
return database;
} catch (Throwable e) {
+ Fawe.debug("============ NO JDBC DRIVER! ============");
+ Fawe.debug("TODO: Bundle driver with FAWE (or disable database)");
+ Fawe.debug("=========================================");
e.printStackTrace();
+ Fawe.debug("=========================================");
return null;
}
}
diff --git a/core/src/main/java/com/boydti/fawe/installer/BrowseButton.java b/core/src/main/java/com/boydti/fawe/installer/BrowseButton.java
index b6fd4271..2eabc164 100644
--- a/core/src/main/java/com/boydti/fawe/installer/BrowseButton.java
+++ b/core/src/main/java/com/boydti/fawe/installer/BrowseButton.java
@@ -1,8 +1,12 @@
package com.boydti.fawe.installer;
+import com.boydti.fawe.Fawe;
import java.awt.event.ActionEvent;
import java.io.File;
-import javax.swing.JFileChooser;
+import java.util.prefs.Preferences;
+import javafx.application.Platform;
+import javafx.embed.swing.JFXPanel;
+import javafx.stage.DirectoryChooser;
public abstract class BrowseButton extends InteractiveButton {
public BrowseButton() {
@@ -13,12 +17,24 @@ public abstract class BrowseButton extends InteractiveButton {
@Override
public void actionPerformed(ActionEvent e) {
- JFileChooser chooser = new JFileChooser();
- chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
- int returnValue = chooser.showOpenDialog(null);
- if (returnValue == JFileChooser.APPROVE_OPTION) {
- File selectedFile = chooser.getSelectedFile();
- onSelect(selectedFile);
- }
+ Preferences prefs = Preferences.userRoot().node(Fawe.class.getName());
+ String lastUsed = prefs.get("LAST_USED_FOLDER", null);
+ final File lastFile = lastUsed == null ? null : new File(lastUsed).getParentFile();
+ browse(lastFile);
+ }
+
+ public void browse(File from) {
+ DirectoryChooser folderChooser = new DirectoryChooser();
+ folderChooser.setInitialDirectory(from);
+
+ new JFXPanel(); // Init JFX Platform
+ Platform.runLater(() -> {
+ File file = folderChooser.showDialog(null);
+ if (file != null && file.exists()) {
+ File parent = file.getParentFile();
+ Preferences.userRoot().node(Fawe.class.getName()).put("LAST_USED_FOLDER", file.getPath());
+ onSelect(file);
+ }
+ });
}
}
diff --git a/core/src/main/java/com/boydti/fawe/installer/CloseButton.java b/core/src/main/java/com/boydti/fawe/installer/CloseButton.java
index 40e9676f..2fa285cb 100644
--- a/core/src/main/java/com/boydti/fawe/installer/CloseButton.java
+++ b/core/src/main/java/com/boydti/fawe/installer/CloseButton.java
@@ -1,10 +1,12 @@
package com.boydti.fawe.installer;
+import java.awt.Color;
import java.awt.event.ActionEvent;
public class CloseButton extends InteractiveButton {
public CloseButton() {
super("X");
+ setColor(new Color(0x66, 0x33, 0x33));
}
@Override
diff --git a/core/src/main/java/com/boydti/fawe/installer/ImagePanel.java b/core/src/main/java/com/boydti/fawe/installer/ImagePanel.java
new file mode 100644
index 00000000..28ede172
--- /dev/null
+++ b/core/src/main/java/com/boydti/fawe/installer/ImagePanel.java
@@ -0,0 +1,30 @@
+package com.boydti.fawe.installer;
+
+
+import java.awt.AlphaComposite;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.image.BufferedImage;
+import javax.swing.JPanel;
+
+public class ImagePanel extends JPanel {
+
+ private BufferedImage image;
+
+ public ImagePanel(BufferedImage image) {
+ this.image = image;
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ Graphics2D g2d = (Graphics2D) g;
+ g2d.setRenderingHint(
+ RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ g2d.setComposite(AlphaComposite.getInstance(
+ AlphaComposite.SRC_OVER, 0.6f));
+ g.drawImage(image, 0, 0, getWidth(), getWidth(), this); // see javadoc for more info on the parameters
+ }
+
+}
diff --git a/core/src/main/java/com/boydti/fawe/installer/InstallerFrame.java b/core/src/main/java/com/boydti/fawe/installer/InstallerFrame.java
index e400bfac..a63b7e6b 100644
--- a/core/src/main/java/com/boydti/fawe/installer/InstallerFrame.java
+++ b/core/src/main/java/com/boydti/fawe/installer/InstallerFrame.java
@@ -74,7 +74,6 @@ public class InstallerFrame extends JFrame {
title.setAlignmentX(Component.RIGHT_ALIGNMENT);
title.setForeground(LIGHT_GRAY);
-
MinimizeButton minimize = new MinimizeButton(this);
CloseButton exit = new CloseButton();
@@ -154,7 +153,7 @@ public class InstallerFrame extends JFrame {
String date = new Date(100 + version.year, version.month, version.day).toGMTString();
String build = "https://ci.athion.net/job/FastAsyncWorldEdit/" + version.build;
String commit = "https://github.com/boy0001/FastAsyncWorldedit/commit/" + Integer.toHexString(version.hash);
- String footerMessage = "FAWE v" + version.major + "." + version.minor + "." + version.patch + " by Empire92 (c) 2016 (GPL v3.0)";
+ String footerMessage = "FAWE v" + version.major + "." + version.minor + "." + version.patch + " by Empire92 (c) 2017 (GPL v3.0)";
URL licenseUrl = new URL("https://github.com/boy0001/FastAsyncWorldedit/blob/master/LICENSE");
URLButton licenseButton = new URLButton(licenseUrl, footerMessage);
bottomBar.add(licenseButton);
@@ -330,7 +329,7 @@ public class InstallerFrame extends JFrame {
} catch (Throwable e) {
prompt("[ERROR] Copy installer failed, please copy this installer jar manually");
}
- prompt("Installation comlete!\nLaunch the game using the forge profile.");
+ prompt("Installation complete!\nLaunch the game using the forge profile.");
}
});
installThread.start();
diff --git a/core/src/main/java/com/boydti/fawe/installer/InteractiveButton.java b/core/src/main/java/com/boydti/fawe/installer/InteractiveButton.java
index 8faa0868..5172888d 100644
--- a/core/src/main/java/com/boydti/fawe/installer/InteractiveButton.java
+++ b/core/src/main/java/com/boydti/fawe/installer/InteractiveButton.java
@@ -8,7 +8,7 @@ import java.awt.event.MouseListener;
import javax.swing.JButton;
public class InteractiveButton extends JButton implements ActionListener, MouseListener {
- private final Color background;
+ private Color background;
public InteractiveButton(String text) {
this(text, new Color(0, 0, 0, 0));
@@ -17,8 +17,6 @@ public class InteractiveButton extends JButton implements ActionListener, MouseL
public InteractiveButton(String text, Color background) {
setText(text);
setBorderPainted(false);
- setBackground(background);
- setFocusable(false);
setVisible(true);
setForeground(new Color(200, 200, 200));
addActionListener(this);
@@ -27,8 +25,14 @@ public class InteractiveButton extends JButton implements ActionListener, MouseL
if (background.getAlpha() != 0) {
this.background = background;
} else {
- this.background = new Color(0x33, 0x33, 0x36);
+ this.background = new Color(0x38, 0x38, 0x39);
}
+ setBackground(this.background);
+ }
+
+ public void setColor(Color background) {
+ setBackground(background);
+ this.background = background;
}
@Override
diff --git a/core/src/main/java/com/boydti/fawe/installer/JSystemFileChooser.java b/core/src/main/java/com/boydti/fawe/installer/JSystemFileChooser.java
new file mode 100644
index 00000000..f26736b9
--- /dev/null
+++ b/core/src/main/java/com/boydti/fawe/installer/JSystemFileChooser.java
@@ -0,0 +1,60 @@
+package com.boydti.fawe.installer;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Container;
+import javax.swing.JFileChooser;
+import javax.swing.LookAndFeel;
+import javax.swing.UIManager;
+import javax.swing.UnsupportedLookAndFeelException;
+import sun.swing.FilePane;
+
+public class JSystemFileChooser extends JFileChooser {
+ public void updateUI(){
+ LookAndFeel old = UIManager.getLookAndFeel();
+ try {
+ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ }
+ catch (Throwable ex) {
+ old = null;
+ }
+
+ super.updateUI();
+
+ if(old != null){
+ FilePane filePane = findFilePane(this);
+ filePane.setViewType(FilePane.VIEWTYPE_DETAILS);
+ filePane.setViewType(FilePane.VIEWTYPE_LIST);
+
+ Color background = UIManager.getColor("Label.background");
+ setBackground(background);
+ setOpaque(true);
+
+ try {
+ UIManager.setLookAndFeel(old);
+ }
+ catch (UnsupportedLookAndFeelException ignored) {} // shouldn't get here
+ }
+ }
+
+
+
+ private static FilePane findFilePane(Container parent){
+ for(Component comp: parent.getComponents()){
+ if(FilePane.class.isInstance(comp)){
+ return (FilePane)comp;
+ }
+ if(comp instanceof Container){
+ Container cont = (Container)comp;
+ if(cont.getComponentCount() > 0){
+ FilePane found = findFilePane(cont);
+ if (found != null) {
+ return found;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/core/src/main/java/com/boydti/fawe/installer/MovablePanel.java b/core/src/main/java/com/boydti/fawe/installer/MovablePanel.java
index dd82a987..528a6266 100644
--- a/core/src/main/java/com/boydti/fawe/installer/MovablePanel.java
+++ b/core/src/main/java/com/boydti/fawe/installer/MovablePanel.java
@@ -1,17 +1,17 @@
package com.boydti.fawe.installer;
-import java.awt.Component;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
+import javax.swing.JFrame;
import javax.swing.JPanel;
public class MovablePanel extends JPanel {
private Point initialClick;
- private Component parent;
+ private JFrame parent;
- public MovablePanel(final Component parent) {
+ public MovablePanel(final JFrame parent) {
this.parent = parent;
addMouseListener(new MouseAdapter() {
diff --git a/core/src/main/java/com/boydti/fawe/installer/TextAreaOutputStream.java b/core/src/main/java/com/boydti/fawe/installer/TextAreaOutputStream.java
index 6a62dae5..7f91c392 100644
--- a/core/src/main/java/com/boydti/fawe/installer/TextAreaOutputStream.java
+++ b/core/src/main/java/com/boydti/fawe/installer/TextAreaOutputStream.java
@@ -1,29 +1,58 @@
package com.boydti.fawe.installer;
+import com.boydti.fawe.config.BBC;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.JTextArea;
public class TextAreaOutputStream extends PrintStream {
public TextAreaOutputStream(final JTextArea textArea) {
super(new OutputStream() {
- private StringBuffer buffer = new StringBuffer();
- private String newLine = "";
+ StringBuffer buffer = new StringBuffer();
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+ AtomicBoolean updated = new AtomicBoolean();
+ AtomicBoolean waiting = new AtomicBoolean();
+ boolean lineColor = false;
@Override
public void write(int b) throws IOException {
- if (b != '\n') {
- buffer.append((char) b);
+ buffer.append((char) b);
+ if (b == '\n') {
+ updated.set(true);
+ if (waiting.compareAndSet(false, true)) {
+ executor.submit(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ updated.set(false);
+ int len = buffer.length();
+ textArea.append(BBC.stripColor(buffer.substring(0, len)));
+ buffer.delete(0, len);
+ textArea.setVisible(true);
+ textArea.repaint();
+ } finally {
+ waiting.set(false);
+ if (updated.get() && waiting.compareAndSet(false, true)) {
+ executor.submit(this);
+ }
+ }
+ }
+ });
+ }
} else {
- textArea.setText(buffer + newLine + textArea.getText());
- newLine = "\n";
- buffer.delete(0, buffer.length());
- textArea.setVisible(true);
- textArea.repaint();
+ updated.lazySet(true);
}
}
+
+ @Override
+ protected void finalize() throws Throwable {
+ executor.shutdownNow();
+ }
});
}
}
diff --git a/core/src/main/java/com/boydti/fawe/jnbt/CorruptSchematicStreamer.java b/core/src/main/java/com/boydti/fawe/jnbt/CorruptSchematicStreamer.java
index d5a8d222..e0b2e35f 100644
--- a/core/src/main/java/com/boydti/fawe/jnbt/CorruptSchematicStreamer.java
+++ b/core/src/main/java/com/boydti/fawe/jnbt/CorruptSchematicStreamer.java
@@ -96,141 +96,161 @@ public class CorruptSchematicStreamer {
}
public Clipboard recover() {
- if (stream == null || !stream.markSupported()) {
- throw new IllegalArgumentException("Can only recover from a marked and resettable stream!");
- }
- match("Width", new CorruptSchematicStreamer.CorruptReader() {
- @Override
- public void run(DataInputStream in) throws IOException {
- width.set(in.readShort());
+ try {
+ if (stream == null || !stream.markSupported()) {
+ throw new IllegalArgumentException("Can only recover from a marked and resettable stream!");
}
- });
- match("Height", new CorruptSchematicStreamer.CorruptReader() {
- @Override
- public void run(DataInputStream in) throws IOException {
- height.set(in.readShort());
- }
- });
- match("Length", new CorruptSchematicStreamer.CorruptReader() {
- @Override
- public void run(DataInputStream in) throws IOException {
- length.set(in.readShort());
- }
- });
- match("WEOffsetX", new CorruptSchematicStreamer.CorruptReader() {
- @Override
- public void run(DataInputStream in) throws IOException {
- offsetX.set(in.readInt());
- }
- });
- match("WEOffsetY", new CorruptSchematicStreamer.CorruptReader() {
- @Override
- public void run(DataInputStream in) throws IOException {
- offsetY.set(in.readInt());
- }
- });
- match("WEOffsetZ", new CorruptSchematicStreamer.CorruptReader() {
- @Override
- public void run(DataInputStream in) throws IOException {
- offsetZ.set(in.readInt());
- }
- });
- match("WEOriginX", new CorruptSchematicStreamer.CorruptReader() {
- @Override
- public void run(DataInputStream in) throws IOException {
- originX.set(in.readInt());
- }
- });
- match("WEOriginY", new CorruptSchematicStreamer.CorruptReader() {
- @Override
- public void run(DataInputStream in) throws IOException {
- originY.set(in.readInt());
- }
- });
- match("WEOriginZ", new CorruptSchematicStreamer.CorruptReader() {
- @Override
- public void run(DataInputStream in) throws IOException {
- originZ.set(in.readInt());
- }
- });
- match("Blocks", new CorruptSchematicStreamer.CorruptReader() {
- @Override
- public void run(DataInputStream in) throws IOException {
- int length = in.readInt();
- volume.set(length);
- setupClipboard();
- for (int i = 0; i < length; i++) {
- fc.setId(i, in.read());
+ match("Width", new CorruptSchematicStreamer.CorruptReader() {
+ @Override
+ public void run(DataInputStream in) throws IOException {
+ width.set(in.readShort());
}
- }
- });
- match("Data", new CorruptSchematicStreamer.CorruptReader() {
- @Override
- public void run(DataInputStream in) throws IOException {
- int length = in.readInt();
- volume.set(length);
- setupClipboard();
- for (int i = 0; i < length; i++) {
- fc.setData(i, in.read());
+ });
+ match("Height", new CorruptSchematicStreamer.CorruptReader() {
+ @Override
+ public void run(DataInputStream in) throws IOException {
+ height.set(in.readShort());
}
- }
- });
- match("Add", new CorruptSchematicStreamer.CorruptReader() {
- @Override
- public void run(DataInputStream in) throws IOException {
- int length = in.readInt();
- volume.set(length);
- setupClipboard();
- for (int i = 0; i < length; i++) {
- fc.setAdd(i, in.read());
+ });
+ match("Length", new CorruptSchematicStreamer.CorruptReader() {
+ @Override
+ public void run(DataInputStream in) throws IOException {
+ length.set(in.readShort());
}
- }
- });
- Vector dimensions = guessDimensions(volume.get(), width.get(), height.get(), length.get());
- Vector min = new Vector(originX.get(), originY.get(), originZ.get());
- Vector offset = new Vector(offsetX.get(), offsetY.get(), offsetZ.get());
- Vector origin = min.subtract(offset);
- CuboidRegion region = new CuboidRegion(min, min.add(dimensions.getBlockX(), dimensions.getBlockY(), dimensions.getBlockZ()).subtract(Vector.ONE));
- fc.setOrigin(offset);
- final BlockArrayClipboard clipboard = new BlockArrayClipboard(region, fc);
- match("TileEntities", new CorruptSchematicStreamer.CorruptReader() {
- @Override
- public void run(DataInputStream in) throws IOException {
- int childType = in.readByte();
- int length = in.readInt();
- NBTInputStream nis = new NBTInputStream(in);
- for (int i = 0; i < length; ++i) {
- CompoundTag tag = (CompoundTag) nis.readTagPayload(childType, 1);
- int x = tag.getInt("x");
- int y = tag.getInt("y");
- int z = tag.getInt("z");
- fc.setTile(x, y, z, tag);
+ });
+ match("WEOffsetX", new CorruptSchematicStreamer.CorruptReader() {
+ @Override
+ public void run(DataInputStream in) throws IOException {
+ offsetX.set(in.readInt());
}
- }
- });
- match("Entities", new CorruptSchematicStreamer.CorruptReader() {
- @Override
- public void run(DataInputStream in) throws IOException {
- int childType = in.readByte();
- int length = in.readInt();
- NBTInputStream nis = new NBTInputStream(in);
- for (int i = 0; i < length; ++i) {
- CompoundTag tag = (CompoundTag) nis.readTagPayload(childType, 1);
- int x = tag.getInt("x");
- int y = tag.getInt("y");
- int z = tag.getInt("z");
- String id = tag.getString("id");
- if (id.isEmpty()) {
- return;
+ });
+ match("WEOffsetY", new CorruptSchematicStreamer.CorruptReader() {
+ @Override
+ public void run(DataInputStream in) throws IOException {
+ offsetY.set(in.readInt());
+ }
+ });
+ match("WEOffsetZ", new CorruptSchematicStreamer.CorruptReader() {
+ @Override
+ public void run(DataInputStream in) throws IOException {
+ offsetZ.set(in.readInt());
+ }
+ });
+ match("WEOriginX", new CorruptSchematicStreamer.CorruptReader() {
+ @Override
+ public void run(DataInputStream in) throws IOException {
+ originX.set(in.readInt());
+ }
+ });
+ match("WEOriginY", new CorruptSchematicStreamer.CorruptReader() {
+ @Override
+ public void run(DataInputStream in) throws IOException {
+ originY.set(in.readInt());
+ }
+ });
+ match("WEOriginZ", new CorruptSchematicStreamer.CorruptReader() {
+ @Override
+ public void run(DataInputStream in) throws IOException {
+ originZ.set(in.readInt());
+ }
+ });
+ match("Blocks", new CorruptSchematicStreamer.CorruptReader() {
+ @Override
+ public void run(DataInputStream in) throws IOException {
+ int length = in.readInt();
+ volume.set(length);
+ setupClipboard();
+ for (int i = 0; i < length; i++) {
+ fc.setId(i, in.read());
}
- ListTag positionTag = tag.getListTag("Pos");
- ListTag directionTag = tag.getListTag("Rotation");
- BaseEntity state = new BaseEntity(id, tag);
- fc.createEntity(clipboard, positionTag.asDouble(0), positionTag.asDouble(1), positionTag.asDouble(2), (float) directionTag.asDouble(0), (float) directionTag.asDouble(1), state);
}
- }
- });
- return clipboard;
+ });
+ match("Data", new CorruptSchematicStreamer.CorruptReader() {
+ @Override
+ public void run(DataInputStream in) throws IOException {
+ int length = in.readInt();
+ volume.set(length);
+ setupClipboard();
+ for (int i = 0; i < length; i++) {
+ fc.setData(i, in.read());
+ }
+ }
+ });
+ match("Add", new CorruptSchematicStreamer.CorruptReader() {
+ @Override
+ public void run(DataInputStream in) throws IOException {
+ int length = in.readInt();
+ int expected = volume.get();
+ if (expected == 0) {
+ volume.set(length * 2);
+ }
+ setupClipboard();
+ if (expected != length) {
+ for (int i = 0; i < length; i++) {
+ int value = in.read();
+ int first = value & 0x0F;
+ int second = (value & 0xF0) >> 4;
+ int gIndex = i << 1;
+ if (first != 0) fc.setAdd(gIndex, first);
+ if (second != 0) fc.setAdd(gIndex + 1, second);
+ }
+ } else {
+ for (int i = 0; i < length; i++) {
+ int value = in.read();
+ if (value != 0) fc.setAdd(i, value);
+ }
+ }
+ }
+ });
+ Vector dimensions = guessDimensions(volume.get(), width.get(), height.get(), length.get());
+ Vector min = new Vector(originX.get(), originY.get(), originZ.get());
+ Vector offset = new Vector(offsetX.get(), offsetY.get(), offsetZ.get());
+ Vector origin = min.subtract(offset);
+ CuboidRegion region = new CuboidRegion(min, min.add(dimensions.getBlockX(), dimensions.getBlockY(), dimensions.getBlockZ()).subtract(Vector.ONE));
+ fc.setOrigin(offset);
+ final BlockArrayClipboard clipboard = new BlockArrayClipboard(region, fc);
+ match("TileEntities", new CorruptSchematicStreamer.CorruptReader() {
+ @Override
+ public void run(DataInputStream in) throws IOException {
+ int childType = in.readByte();
+ int length = in.readInt();
+ NBTInputStream nis = new NBTInputStream(in);
+ for (int i = 0; i < length; ++i) {
+ CompoundTag tag = (CompoundTag) nis.readTagPayload(childType, 1);
+ int x = tag.getInt("x");
+ int y = tag.getInt("y");
+ int z = tag.getInt("z");
+ fc.setTile(x, y, z, tag);
+ }
+ }
+ });
+ match("Entities", new CorruptSchematicStreamer.CorruptReader() {
+ @Override
+ public void run(DataInputStream in) throws IOException {
+ int childType = in.readByte();
+ int length = in.readInt();
+ NBTInputStream nis = new NBTInputStream(in);
+ for (int i = 0; i < length; ++i) {
+ CompoundTag tag = (CompoundTag) nis.readTagPayload(childType, 1);
+ int x = tag.getInt("x");
+ int y = tag.getInt("y");
+ int z = tag.getInt("z");
+ String id = tag.getString("id");
+ if (id.isEmpty()) {
+ return;
+ }
+ ListTag positionTag = tag.getListTag("Pos");
+ ListTag directionTag = tag.getListTag("Rotation");
+ BaseEntity state = new BaseEntity(id, tag);
+ fc.createEntity(clipboard, positionTag.asDouble(0), positionTag.asDouble(1), positionTag.asDouble(2), (float) directionTag.asDouble(0), (float) directionTag.asDouble(1), state);
+ }
+ }
+ });
+ return clipboard;
+ } catch (Throwable e) {
+ if (fc != null) fc.close();
+ throw e;
+ }
}
private Vector guessDimensions(int volume, int width, int height, int length) {
diff --git a/core/src/main/java/com/boydti/fawe/jnbt/SchematicStreamer.java b/core/src/main/java/com/boydti/fawe/jnbt/SchematicStreamer.java
index 345278a6..fc1ee1c8 100644
--- a/core/src/main/java/com/boydti/fawe/jnbt/SchematicStreamer.java
+++ b/core/src/main/java/com/boydti/fawe/jnbt/SchematicStreamer.java
@@ -51,7 +51,13 @@ public class SchematicStreamer extends NBTStreamer {
addReader("Schematic.AddBlocks.#", new ByteReader() {
@Override
public void run(int index, int value) {
- if (value != 0) fc.setAdd(index, value);
+ if (value != 0) {
+ int first = value & 0x0F;
+ int second = (value & 0xF0) >> 4;
+ int gIndex = index << 1;
+ if (first != 0) fc.setAdd(gIndex, first);
+ if (second != 0) fc.setAdd(gIndex + 1, second);
+ }
}
});
@@ -192,17 +198,24 @@ public class SchematicStreamer extends NBTStreamer {
}
public Clipboard getClipboard() throws IOException {
- addDimensionReaders();
- addBlockReaders();
- readFully();
- Vector min = new Vector(originX, originY, originZ);
- Vector offset = new Vector(offsetX, offsetY, offsetZ);
- Vector origin = min.subtract(offset);
- Vector dimensions = new Vector(width, height, length);
- fc.setDimensions(dimensions);
- CuboidRegion region = new CuboidRegion(min, min.add(width, height, length).subtract(Vector.ONE));
- clipboard.init(region, fc);
- clipboard.setOrigin(origin);
- return clipboard;
+ try {
+ addDimensionReaders();
+ addBlockReaders();
+ readFully();
+ Vector min = new Vector(originX, originY, originZ);
+ Vector offset = new Vector(offsetX, offsetY, offsetZ);
+ Vector origin = min.subtract(offset);
+ Vector dimensions = new Vector(width, height, length);
+ fc.setDimensions(dimensions);
+ CuboidRegion region = new CuboidRegion(min, min.add(width, height, length).subtract(Vector.ONE));
+ clipboard.init(region, fc);
+ clipboard.setOrigin(origin);
+ return clipboard;
+ } catch (Throwable e) {
+ if (fc != null) {
+ fc.close();
+ }
+ throw e;
+ }
}
}
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 64ad57fd..78b417e1 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
@@ -17,6 +17,7 @@ import com.sk89q.jnbt.NBTConstants;
import com.sk89q.jnbt.NBTInputStream;
import com.sk89q.jnbt.NBTOutputStream;
import com.sk89q.jnbt.Tag;
+import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
@@ -106,7 +107,7 @@ public class MCAChunk extends FaweChunk {
}
FastByteArrayOutputStream buffered = new FastByteArrayOutputStream(buffer);
DataOutputStream dataOut = new DataOutputStream(buffered);
- NBTOutputStream nbtOut = new NBTOutputStream(dataOut);
+ NBTOutputStream nbtOut = new NBTOutputStream((DataOutput) dataOut);
nbtOut.writeNamedTagName("", NBTConstants.TYPE_COMPOUND);
nbtOut.writeLazyCompoundTag("Level", new NBTOutputStream.LazyWrite() {
@Override
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 b6bcbdf4..29b43be2 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
@@ -98,6 +98,9 @@ public class MCAFile {
chunks.clear();
}
locations = null;
+ IterableThreadLocal.clean(byteStore1);
+ IterableThreadLocal.clean(byteStore2);
+ IterableThreadLocal.clean(byteStore3);
}
@Override
@@ -400,7 +403,7 @@ public class MCAFile {
if (raf.length() - offset < len) {
raf.setLength(((offset + len + 4095) / 4096) * 4096);
}
- raf.writeInt(data.length);
+ raf.writeInt(data.length + 1);
raf.write(2);
raf.write(data);
}
diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAFilter.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAFilter.java
index 26e82c98..21b28366 100644
--- a/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAFilter.java
+++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAFilter.java
@@ -83,4 +83,14 @@ public class MCAFilter extends IterableThreadLocal {
*/
public void finishChunk(MCAChunk chunk, T cache) {
}
+
+ /**
+ * Do something with the MCAChunk after block filtering
+ *
+ * @param chunk
+ * @param cache
+ * @return
+ */
+ public void finishFile(MCAFile file, T cache) {
+ }
}
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 87711bed..8c8d935c 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
@@ -4,6 +4,7 @@ import com.boydti.fawe.Fawe;
import com.boydti.fawe.example.CharFaweChunk;
import com.boydti.fawe.example.NMSMappedFaweQueue;
import com.boydti.fawe.example.NullFaweChunk;
+import com.boydti.fawe.jnbt.anvil.filters.DelegateMCAFilter;
import com.boydti.fawe.object.FaweChunk;
import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.object.FaweQueue;
@@ -15,7 +16,6 @@ import com.boydti.fawe.object.collection.IterableThreadLocal;
import com.boydti.fawe.util.MainUtil;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.worldedit.Vector;
-import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.world.biome.BaseBiome;
import java.io.File;
import java.io.IOException;
@@ -274,17 +274,7 @@ public class MCAQueue extends NMSMappedFaweQueue> T filterCopy(final T filter, boolean deleteOnCopyFail) {
- this.filterWorld(new MCAFilter() {
- @Override
- public boolean appliesFile(int mcaX, int mcaZ) {
- return filter.appliesFile(mcaX, mcaZ);
- }
-
- @Override
- public boolean appliesFile(Path path, BasicFileAttributes attr) {
- return filter.appliesFile(path, attr);
- }
-
+ this.filterWorld(new DelegateMCAFilter(filter) {
@Override
public MCAFile applyFile(MCAFile mca) {
File file = mca.getFile();
@@ -328,21 +318,6 @@ public class MCAQueue extends NMSMappedFaweQueue() {
@Override
public void run(MCAFile value) {
@@ -360,7 +335,7 @@ public class MCAQueue extends NMSMappedFaweQueue> T filterRegion(final T filter, final RegionWrapper region) {
- this.filterWorld(new MCAFilter() {
+ this.filterWorld(new DelegateMCAFilter(filter) {
@Override
public boolean appliesFile(Path path, BasicFileAttributes attr) {
@@ -376,11 +351,6 @@ public class MCAQueue extends NMSMappedFaweQueue> 5);
raf.seek(offset);
- raf.writeInt(compressedBytes.length);
+ raf.writeInt(compressedBytes.length + 1);
raf.write(2);
raf.write(compressedBytes);
offset += blocks * 4096;
diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/MutableMCABackedBaseBlock.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MutableMCABackedBaseBlock.java
index 54d17e08..c7ef0dea 100644
--- a/core/src/main/java/com/boydti/fawe/jnbt/anvil/MutableMCABackedBaseBlock.java
+++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MutableMCABackedBaseBlock.java
@@ -31,6 +31,10 @@ public class MutableMCABackedBaseBlock extends BaseBlock {
data = chunk.data[layer];
}
+ public MCAChunk getChunk() {
+ return chunk;
+ }
+
public void setX(int x) {
this.x = x;
}
diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/DelegateMCAFilter.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/DelegateMCAFilter.java
new file mode 100644
index 00000000..8131cbe3
--- /dev/null
+++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/DelegateMCAFilter.java
@@ -0,0 +1,93 @@
+package com.boydti.fawe.jnbt.anvil.filters;
+
+import com.boydti.fawe.jnbt.anvil.MCAChunk;
+import com.boydti.fawe.jnbt.anvil.MCAFile;
+import com.boydti.fawe.jnbt.anvil.MCAFilter;
+import com.sk89q.worldedit.blocks.BaseBlock;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Spliterator;
+import java.util.function.Consumer;
+
+public class DelegateMCAFilter extends MCAFilter {
+ private final MCAFilter filter;
+
+ @Override
+ public boolean appliesFile(Path path, BasicFileAttributes attr) {
+ return filter.appliesFile(path, attr);
+ }
+
+ @Override
+ public boolean appliesFile(int mcaX, int mcaZ) {
+ return filter.appliesFile(mcaX, mcaZ);
+ }
+
+ @Override
+ public MCAFile applyFile(MCAFile file) {
+ return filter.applyFile(file);
+ }
+
+ @Override
+ public boolean appliesChunk(int cx, int cz) {
+ return filter.appliesChunk(cx, cz);
+ }
+
+ @Override
+ public MCAChunk applyChunk(MCAChunk chunk, T cache) {
+ return filter.applyChunk(chunk, cache);
+ }
+
+ @Override
+ public void applyBlock(int x, int y, int z, BaseBlock block, T cache) {
+ filter.applyBlock(x, y, z, block, cache);
+ }
+
+ @Override
+ public void finishChunk(MCAChunk chunk, T cache) {
+ filter.finishChunk(chunk, cache);
+ }
+
+ @Override
+ public void finishFile(MCAFile file, T cache) {
+ filter.finishFile(file, cache);
+ }
+
+ @Override
+ public T init() {
+ return filter.init();
+ }
+
+ @Override
+ public void clean() {
+ filter.clean();
+ }
+
+ @Override
+ public T get() {
+ return filter.get();
+ }
+
+ @Override
+ public void forEach(Consumer super T> action) {
+ filter.forEach(action);
+ }
+
+ @Override
+ public Spliterator spliterator() {
+ return filter.spliterator();
+ }
+
+ @Override
+ public void remove() {
+ filter.remove();
+ }
+
+ @Override
+ public void set(T value) {
+ filter.set(value);
+ }
+
+ public DelegateMCAFilter(MCAFilter filter) {
+ this.filter = filter;
+ }
+}
diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/RemapFilter.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/RemapFilter.java
new file mode 100644
index 00000000..b35439ed
--- /dev/null
+++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/RemapFilter.java
@@ -0,0 +1,123 @@
+package com.boydti.fawe.jnbt.anvil.filters;
+
+import com.boydti.fawe.FaweCache;
+import com.boydti.fawe.jnbt.anvil.MCAChunk;
+import com.boydti.fawe.jnbt.anvil.MCAFilterCounter;
+import com.boydti.fawe.jnbt.anvil.MutableMCABackedBaseBlock;
+import com.boydti.fawe.object.clipboard.ClipboardRemapper;
+import com.boydti.fawe.object.number.MutableLong;
+import com.boydti.fawe.util.ReflectionUtils;
+import com.sk89q.jnbt.ByteTag;
+import com.sk89q.jnbt.CompoundTag;
+import com.sk89q.jnbt.Tag;
+import com.sk89q.worldedit.blocks.BaseBlock;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+public class RemapFilter extends MCAFilterCounter {
+ private final ClipboardRemapper remapper;
+ private final ClipboardRemapper.RemapPlatform from;
+
+ public RemapFilter(ClipboardRemapper remapper) {
+ this.remapper = remapper;
+ this.from = null;
+ }
+
+ public RemapFilter(ClipboardRemapper.RemapPlatform from, ClipboardRemapper.RemapPlatform to) {
+ this.remapper = new ClipboardRemapper(from, to);
+ this.from = from;
+ }
+
+ @Override
+ public MCAChunk applyChunk(MCAChunk chunk, MutableLong cache) {
+ return super.applyChunk(chunk, cache);
+ }
+
+ @Override
+ public void applyBlock(int x, int y, int z, BaseBlock block, MutableLong cache) {
+ int id = block.getId();
+ if (remapper.hasRemap(block.getId())) {
+ BaseBlock result = remapper.remap(block);
+ if (result != block) {
+ cache.add(1);
+ block.setId(id = result.getId());
+ if (id == 218) {
+ CompoundTag nbt = block.getNbtData();
+ if (nbt != null) {
+ Map map = ReflectionUtils.getMap(nbt.getValue());
+ map.putIfAbsent("facing", new ByteTag((byte) block.getData()));
+ }
+ }
+ block.setData(result.getData());
+ }
+ }
+ outer:
+ switch (from) {
+ case PC: {
+ int newLight = 0;
+ switch (id) {
+ case 29:
+ case 33:
+ Map map = new HashMap<>();
+ map.put("Progress", 0f);
+ map.put("State", (byte) 0);
+ map.put("LastProgress", 0f);
+ map.put("NewState", (byte) 0);
+ map.put("isMoveable", (byte) 1);
+ map.put("id", "PistonArm");
+ map.put("AttachedBlocks", new ArrayList<>());
+ map.put("Sticky", (byte) (id == 29 ? 1 : 0));
+ map.put("x", x);
+ map.put("y", y);
+ map.put("z", z);
+ block.setNbtData(FaweCache.asTag(map));
+ break;
+ case 44:
+ case 182:
+ case 158:
+ case 53:
+ case 67:
+ case 108:
+ case 109:
+ case 114:
+ case 128:
+ case 134:
+ case 135:
+ case 136:
+ case 156:
+ case 163:
+ case 164:
+ case 180:
+ case 203:
+ case 198:
+ MutableMCABackedBaseBlock mcaBlock = (MutableMCABackedBaseBlock) block;
+ MCAChunk chunk = mcaBlock.getChunk();
+ int currentLight = chunk.getSkyLight(x, y, z);
+ if (currentLight >= 14) {
+ break;
+ }
+ newLight = chunk.getSkyLight(x, (y + 1) & 0xFF, z);
+ if (newLight > currentLight) break;
+ if (x > 0) {
+ if ((newLight = chunk.getSkyLight(x - 1, y, z)) > currentLight) break;
+ }
+ if (x < 16) {
+ if ((newLight = chunk.getSkyLight(x + 1, y, z)) > currentLight) break;
+ }
+ if (z > 0) {
+ if ((newLight = chunk.getSkyLight(x, y, z - 1)) > currentLight) break;
+ }
+ if (z < 16) {
+ if ((newLight = chunk.getSkyLight(x, y, z + 1)) > currentLight) break;
+ }
+ default: break outer;
+ }
+ if (newLight != 0) {
+ ((MutableMCABackedBaseBlock) block).getChunk().setSkyLight(x, y, z, newLight);
+ }
+ break;
+ }
+ }
+ }
+}
diff --git a/core/src/main/java/com/boydti/fawe/logging/rollback/RollbackOptimizedHistory.java b/core/src/main/java/com/boydti/fawe/logging/rollback/RollbackOptimizedHistory.java
index 712a2a5e..50251c4a 100644
--- a/core/src/main/java/com/boydti/fawe/logging/rollback/RollbackOptimizedHistory.java
+++ b/core/src/main/java/com/boydti/fawe/logging/rollback/RollbackOptimizedHistory.java
@@ -1,5 +1,6 @@
package com.boydti.fawe.logging.rollback;
+import com.boydti.fawe.Fawe;
import com.boydti.fawe.database.DBHandler;
import com.boydti.fawe.database.RollbackDatabase;
import com.boydti.fawe.object.changeset.DiskStorageHistory;
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 e6fc1307..5b879ed7 100644
--- a/core/src/main/java/com/boydti/fawe/object/FawePlayer.java
+++ b/core/src/main/java/com/boydti/fawe/object/FawePlayer.java
@@ -66,7 +66,7 @@ public abstract class FawePlayer extends Metadatable {
* @return
*/
public static FawePlayer wrap(Object obj) {
- if (obj == null) {
+ if (obj == null || (obj instanceof String && obj.equals("*"))) {
return FakePlayer.getConsole().toFawePlayer();
}
if (obj instanceof FawePlayer) {
diff --git a/core/src/main/java/com/boydti/fawe/object/changeset/DiskStorageHistory.java b/core/src/main/java/com/boydti/fawe/object/changeset/DiskStorageHistory.java
index c8c0b723..5facd362 100644
--- a/core/src/main/java/com/boydti/fawe/object/changeset/DiskStorageHistory.java
+++ b/core/src/main/java/com/boydti/fawe/object/changeset/DiskStorageHistory.java
@@ -14,6 +14,7 @@ import com.sk89q.jnbt.NBTOutputStream;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.world.World;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+import java.io.DataOutput;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@@ -281,7 +282,7 @@ public class DiskStorageHistory extends FaweStreamChangeSet {
}
enttFile.getParentFile().mkdirs();
enttFile.createNewFile();
- osENTCT = new NBTOutputStream(getCompressedOS(new FileOutputStream(enttFile)));
+ osENTCT = new NBTOutputStream((DataOutput) getCompressedOS(new FileOutputStream(enttFile)));
return osENTCT;
}
@@ -292,7 +293,7 @@ public class DiskStorageHistory extends FaweStreamChangeSet {
}
entfFile.getParentFile().mkdirs();
entfFile.createNewFile();
- osENTCF = new NBTOutputStream(getCompressedOS(new FileOutputStream(entfFile)));
+ osENTCF = new NBTOutputStream((DataOutput) getCompressedOS(new FileOutputStream(entfFile)));
return osENTCF;
}
@@ -303,7 +304,7 @@ public class DiskStorageHistory extends FaweStreamChangeSet {
}
nbttFile.getParentFile().mkdirs();
nbttFile.createNewFile();
- osNBTT = new NBTOutputStream(getCompressedOS(new FileOutputStream(nbttFile)));
+ osNBTT = new NBTOutputStream((DataOutput) getCompressedOS(new FileOutputStream(nbttFile)));
return osNBTT;
}
@@ -314,7 +315,7 @@ public class DiskStorageHistory extends FaweStreamChangeSet {
}
nbtfFile.getParentFile().mkdirs();
nbtfFile.createNewFile();
- osNBTF = new NBTOutputStream(getCompressedOS(new FileOutputStream(nbtfFile)));
+ osNBTF = new NBTOutputStream((DataOutput) getCompressedOS(new FileOutputStream(nbtfFile)));
return osNBTF;
}
diff --git a/core/src/main/java/com/boydti/fawe/object/changeset/MemoryOptimizedHistory.java b/core/src/main/java/com/boydti/fawe/object/changeset/MemoryOptimizedHistory.java
index 2c28da6a..e06f18c9 100644
--- a/core/src/main/java/com/boydti/fawe/object/changeset/MemoryOptimizedHistory.java
+++ b/core/src/main/java/com/boydti/fawe/object/changeset/MemoryOptimizedHistory.java
@@ -9,6 +9,7 @@ import com.boydti.fawe.util.MainUtil;
import com.sk89q.jnbt.NBTInputStream;
import com.sk89q.jnbt.NBTOutputStream;
import com.sk89q.worldedit.world.World;
+import java.io.DataOutput;
import java.io.IOException;
import java.io.OutputStream;
@@ -188,7 +189,7 @@ public class MemoryOptimizedHistory extends FaweStreamChangeSet {
return entCStreamZip;
}
entCStream = new FastByteArrayOutputStream(Settings.IMP.HISTORY.BUFFER_SIZE);
- return entCStreamZip = new NBTOutputStream(getCompressedOS(entCStream));
+ return entCStreamZip = new NBTOutputStream((DataOutput) getCompressedOS(entCStream));
}
@Override
@@ -197,7 +198,7 @@ public class MemoryOptimizedHistory extends FaweStreamChangeSet {
return entRStreamZip;
}
entRStream = new FastByteArrayOutputStream(Settings.IMP.HISTORY.BUFFER_SIZE);
- return entRStreamZip = new NBTOutputStream(getCompressedOS(entRStream));
+ return entRStreamZip = new NBTOutputStream((DataOutput) getCompressedOS(entRStream));
}
@Override
@@ -206,7 +207,7 @@ public class MemoryOptimizedHistory extends FaweStreamChangeSet {
return tileCStreamZip;
}
tileCStream = new FastByteArrayOutputStream(Settings.IMP.HISTORY.BUFFER_SIZE);
- return tileCStreamZip = new NBTOutputStream(getCompressedOS(tileCStream));
+ return tileCStreamZip = new NBTOutputStream((DataOutput) getCompressedOS(tileCStream));
}
@Override
@@ -215,7 +216,7 @@ public class MemoryOptimizedHistory extends FaweStreamChangeSet {
return tileRStreamZip;
}
tileRStream = new FastByteArrayOutputStream(Settings.IMP.HISTORY.BUFFER_SIZE);
- return tileRStreamZip = new NBTOutputStream(getCompressedOS(tileRStream));
+ return tileRStreamZip = new NBTOutputStream((DataOutput) getCompressedOS(tileRStream));
}
@Override
diff --git a/core/src/main/java/com/boydti/fawe/object/clipboard/AbstractDelegateFaweClipboard.java b/core/src/main/java/com/boydti/fawe/object/clipboard/AbstractDelegateFaweClipboard.java
index d8d22772..25efbad0 100644
--- a/core/src/main/java/com/boydti/fawe/object/clipboard/AbstractDelegateFaweClipboard.java
+++ b/core/src/main/java/com/boydti/fawe/object/clipboard/AbstractDelegateFaweClipboard.java
@@ -36,6 +36,11 @@ public class AbstractDelegateFaweClipboard extends FaweClipboard {
parent.setData(index, data);
}
+ @Override
+ public BaseBlock getBlock(int index) {
+ return parent.getBlock(index);
+ }
+
@Override
public void setAdd(int index, int id) {
parent.setAdd(index, id);
diff --git a/core/src/main/java/com/boydti/fawe/object/clipboard/CPUOptimizedClipboard.java b/core/src/main/java/com/boydti/fawe/object/clipboard/CPUOptimizedClipboard.java
index 8395d394..163d7c9c 100644
--- a/core/src/main/java/com/boydti/fawe/object/clipboard/CPUOptimizedClipboard.java
+++ b/core/src/main/java/com/boydti/fawe/object/clipboard/CPUOptimizedClipboard.java
@@ -130,6 +130,7 @@ public class CPUOptimizedClipboard extends FaweClipboard {
return getBlock(index);
}
+ @Override
public BaseBlock getBlock(int index) {
int id = getId(index);
if (add != null) {
diff --git a/core/src/main/java/com/boydti/fawe/object/clipboard/ClipboardRemapper.java b/core/src/main/java/com/boydti/fawe/object/clipboard/ClipboardRemapper.java
new file mode 100644
index 00000000..788a57bd
--- /dev/null
+++ b/core/src/main/java/com/boydti/fawe/object/clipboard/ClipboardRemapper.java
@@ -0,0 +1,230 @@
+package com.boydti.fawe.object.clipboard;
+
+import com.boydti.fawe.FaweCache;
+import com.sk89q.worldedit.BlockVector;
+import com.sk89q.worldedit.WorldEditException;
+import com.sk89q.worldedit.blocks.BaseBlock;
+import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
+import com.sk89q.worldedit.extent.clipboard.Clipboard;
+import com.sk89q.worldedit.regions.Region;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+public class ClipboardRemapper {
+ public enum RemapPlatform {
+ PE,
+ PC
+ }
+
+ public ClipboardRemapper() {
+
+ }
+
+ public ClipboardRemapper(RemapPlatform fromPlatform, RemapPlatform toPlatform) {
+ if (fromPlatform == toPlatform) {
+ return;
+ }
+ HashMap mapPEtoPC = new HashMap<>();
+ mapPEtoPC.put(new BaseBlock(95,-1), new BaseBlock(166,-1));
+ mapPEtoPC.put(new BaseBlock(125,-1), new BaseBlock(158,-1));
+ mapPEtoPC.put(new BaseBlock(126,-1), new BaseBlock(157,-1));
+ mapPEtoPC.put(new BaseBlock(157,-1), new BaseBlock(125,-1));
+ mapPEtoPC.put(new BaseBlock(158,-1), new BaseBlock(126,-1));
+ mapPEtoPC.put(new BaseBlock(188,-1), new BaseBlock(210,-1));
+ mapPEtoPC.put(new BaseBlock(189,-1), new BaseBlock(211,-1));
+ mapPEtoPC.put(new BaseBlock(198,-1), new BaseBlock(208,-1));
+ mapPEtoPC.put(new BaseBlock(207,-1), new BaseBlock(212,-1));
+ mapPEtoPC.put(new BaseBlock(208,-1), new BaseBlock(198,-1));
+ for (int data = 0; data < 16; data++) {
+ mapPEtoPC.put(new BaseBlock(218, data), new BaseBlock(219 + data, -1));
+ }
+
+ mapPEtoPC.put(new BaseBlock(220,-1), new BaseBlock(235,-1));
+ mapPEtoPC.put(new BaseBlock(220,-1), new BaseBlock(235,-1));
+ mapPEtoPC.put(new BaseBlock(221,-1), new BaseBlock(236,-1));
+ mapPEtoPC.put(new BaseBlock(222,-1), new BaseBlock(237,-1));
+ mapPEtoPC.put(new BaseBlock(223,-1), new BaseBlock(238,-1));
+ mapPEtoPC.put(new BaseBlock(224,-1), new BaseBlock(239,-1));
+ mapPEtoPC.put(new BaseBlock(225,-1), new BaseBlock(240,-1));
+ mapPEtoPC.put(new BaseBlock(226,-1), new BaseBlock(241,-1));
+ mapPEtoPC.put(new BaseBlock(227,-1), new BaseBlock(242,-1));
+ mapPEtoPC.put(new BaseBlock(228,-1), new BaseBlock(243,-1));
+ mapPEtoPC.put(new BaseBlock(229,-1), new BaseBlock(244,-1));
+ mapPEtoPC.put(new BaseBlock(231,-1), new BaseBlock(246,-1));
+ mapPEtoPC.put(new BaseBlock(232,-1), new BaseBlock(247,-1));
+ mapPEtoPC.put(new BaseBlock(233,-1), new BaseBlock(248,-1));
+ mapPEtoPC.put(new BaseBlock(234,-1), new BaseBlock(249,-1));
+
+ for (int id = 220; id <= 235; id++) {
+ int pcId = id + 15;
+ int peId = id == 230 ? 219 : id;
+ mapPEtoPC.put(new BaseBlock(peId,3), new BaseBlock(pcId,0));
+ mapPEtoPC.put(new BaseBlock(peId,4), new BaseBlock(pcId,1));
+ mapPEtoPC.put(new BaseBlock(peId,2), new BaseBlock(pcId,2));
+ mapPEtoPC.put(new BaseBlock(peId,5), new BaseBlock(pcId,3));
+ }
+
+ mapPEtoPC.put(new BaseBlock(236,-1), new BaseBlock(251,-1));
+ mapPEtoPC.put(new BaseBlock(237,-1), new BaseBlock(252,-1));
+ mapPEtoPC.put(new BaseBlock(240,-1), new BaseBlock(199,-1));
+ mapPEtoPC.put(new BaseBlock(241,-1), new BaseBlock(95,-1));
+ mapPEtoPC.put(new BaseBlock(243,0), new BaseBlock(3, 2));
+ mapPEtoPC.put(new BaseBlock(244,-1), new BaseBlock(207,-1));
+
+ mapPEtoPC.put(new BaseBlock(251,-1), new BaseBlock(218,-1));
+
+ mapPEtoPC.put(new BaseBlock(168,2), new BaseBlock(168,1));
+ mapPEtoPC.put(new BaseBlock(168,1), new BaseBlock(168,2));
+
+ mapPEtoPC.put(new BaseBlock(44,7), new BaseBlock(44,6));
+ mapPEtoPC.put(new BaseBlock(44,6), new BaseBlock(44,7));
+ // Top variant
+ mapPEtoPC.put(new BaseBlock(44,7 + 8), new BaseBlock(44,6 + 8));
+ mapPEtoPC.put(new BaseBlock(44,6 + 8), new BaseBlock(44,7 + 8));
+
+ mapPEtoPC.put(new BaseBlock(43,7), new BaseBlock(43,6));
+ mapPEtoPC.put(new BaseBlock(43,6), new BaseBlock(43,7));
+
+ mapPEtoPC.put(new BaseBlock(36,-1), new BaseBlock(34, 1));
+ mapPEtoPC.put(new BaseBlock(85, 1), new BaseBlock(188,-1));
+ mapPEtoPC.put(new BaseBlock(85,2), new BaseBlock(189,-1));
+ mapPEtoPC.put(new BaseBlock(85,3), new BaseBlock(190,-1));
+ mapPEtoPC.put(new BaseBlock(85,4), new BaseBlock(192,-1));
+ mapPEtoPC.put(new BaseBlock(85, 5), new BaseBlock(191,-1));
+ mapPEtoPC.put(new BaseBlock(202,-1), new BaseBlock(201,2));
+ mapPEtoPC.put(new BaseBlock(182,1), new BaseBlock(205,-1));
+
+ for (int id : new int[] {208}) { // end rod
+ mapPEtoPC.put(new BaseBlock(id,4), new BaseBlock(id,5));
+ mapPEtoPC.put(new BaseBlock(id,2), new BaseBlock(id,3));
+ mapPEtoPC.put(new BaseBlock(id,5), new BaseBlock(id,4));
+ mapPEtoPC.put(new BaseBlock(id,3), new BaseBlock(id,2));
+ }
+
+ for (int id : new int[] {77, 143}) { // button
+ mapPEtoPC.put(new BaseBlock(id,4), new BaseBlock(id,2));
+ mapPEtoPC.put(new BaseBlock(id,1), new BaseBlock(id,5));
+ mapPEtoPC.put(new BaseBlock(id,2), new BaseBlock(id,4));
+ mapPEtoPC.put(new BaseBlock(id,5), new BaseBlock(id,1));
+ }
+
+ // leaves
+ for (int data = 4; data < 8; data++) mapPEtoPC.put(new BaseBlock(18,data + 4), new BaseBlock(18,data));
+ for (int data = 4; data < 8; data++) mapPEtoPC.put(new BaseBlock(18,data + 8), new BaseBlock(161,data));
+
+ for (int id : new int[] {96, 167}) { // trapdoor
+ for (int data = 4; data < 12; data++) mapPEtoPC.put(new BaseBlock(id, data), new BaseBlock(id, 15 - data));
+ for (int data = 12; data < 15; data++) mapPEtoPC.put(new BaseBlock(id, data), new BaseBlock(id, 27 - data));
+ }
+
+ // TODO any custom ids
+ switch (fromPlatform) {
+ case PE:
+ break;
+ case PC:
+ break;
+ }
+
+ for (Map.Entry entry : mapPEtoPC.entrySet()) {
+ BaseBlock from = entry.getKey();
+ BaseBlock to = entry.getValue();
+ if (fromPlatform == RemapPlatform.PE) {
+ add(from, to);
+ } else {
+ add(to, from);
+ }
+ }
+ }
+
+ public void apply(Clipboard clipboard) throws WorldEditException {
+ if (clipboard instanceof BlockArrayClipboard) {
+ BlockArrayClipboard bac = (BlockArrayClipboard) clipboard;
+ bac.IMP = new RemappedClipboard(bac.IMP, this);
+ } else {
+ Region region = clipboard.getRegion();
+ for (BlockVector pos : region) {
+ BaseBlock block = clipboard.getBlock(pos);
+ BaseBlock newBlock = remap(block);
+ if (block != newBlock) {
+ clipboard.setBlock(pos, newBlock);
+ }
+ }
+ }
+ }
+
+ private char[] remapCombined = new char[Character.MAX_VALUE + 1];
+ private boolean[] remap = new boolean[Character.MAX_VALUE + 1];
+ private boolean[] remapIds = new boolean[4096];
+ private boolean[] remapAllIds = new boolean[4096];
+ private boolean[] remapAnyIds = new boolean[4096];
+ private boolean[] remapData = new boolean[16];
+
+ public boolean hasRemapData(int data) {
+ return remapData[data];
+ }
+
+ public boolean hasRemapId(int id) {
+ return remapAnyIds[id];
+ }
+
+ public boolean hasRemap(int id) {
+ return remapIds[id];
+ }
+
+ public int remapId(int id) {
+ if (remapAllIds[id]) {
+ return remapCombined[id << 4] >> 4;
+ }
+ return id;
+ }
+
+
+ public void add(BaseBlock from, BaseBlock to) {
+ if (from.getData() != to.getData()) {
+ if (from.getData() == -1) {
+ Arrays.fill(remapData, true);
+ } else {
+ remapData[from.getData()] = true;
+ }
+ }
+ if (from.getData() == -1) {
+ for (int data = 0; data < 16; data++) {
+ int combinedFrom = (from.getId() << 4) + data;
+ int combinedTo = to.getData() == -1 ? (to.getId() << 4) + data : to.getCombined();
+ remap[combinedFrom] = true;
+ remapCombined[combinedFrom] = (char) combinedTo;
+ remapIds[combinedFrom >> 4] = true;
+ if (from.getId() != to.getId()) {
+ remapAnyIds[combinedFrom >> 4] = true;
+ remapAllIds[from.getId()] = true;
+ }
+ }
+ } else {
+ int data = from.getData();
+ int combinedFrom = (from.getId() << 4) + data;
+ int combinedTo = to.getData() == -1 ? (to.getId() << 4) + data : to.getCombined();
+ remap[combinedFrom] = true;
+ remapCombined[combinedFrom] = (char) combinedTo;
+ remapIds[combinedFrom >> 4] = true;
+ if (from.getId() != to.getId()) {
+ remapAnyIds[combinedFrom >> 4] = true;
+ remapAllIds[from.getId()] = false;
+ }
+ }
+ }
+
+
+
+ public BaseBlock remap(BaseBlock block) {
+ int combined = block.getCombined();
+ if (remap[combined]) {
+ char value = remapCombined[combined];
+ BaseBlock newBlock = FaweCache.CACHE_BLOCK[value];
+ newBlock.setNbtData(block.getNbtData());
+ return newBlock;
+ }
+ return block;
+ }
+
+}
diff --git a/core/src/main/java/com/boydti/fawe/object/clipboard/DiskOptimizedClipboard.java b/core/src/main/java/com/boydti/fawe/object/clipboard/DiskOptimizedClipboard.java
index 96c19fa9..f84f9860 100644
--- a/core/src/main/java/com/boydti/fawe/object/clipboard/DiskOptimizedClipboard.java
+++ b/core/src/main/java/com/boydti/fawe/object/clipboard/DiskOptimizedClipboard.java
@@ -372,7 +372,7 @@ public class DiskOptimizedClipboard extends FaweClipboard implements Closeable {
last = i;
int combinedId = mbb.getChar();
BaseBlock block = FaweCache.CACHE_BLOCK[combinedId];
- if (block.canStoreNBTData()) {
+ if (block.canStoreNBTData() && !nbtMap.isEmpty()) {
CompoundTag nbt = nbtMap.get(new IntegerTrio(x, y, z));
if (nbt != null) {
block = new BaseBlock(block.getId(), block.getData());
@@ -386,6 +386,47 @@ public class DiskOptimizedClipboard extends FaweClipboard implements Closeable {
return EditSession.nullBlock;
}
+ @Override
+ public BaseBlock getBlock(int i) {
+ try {
+ if (i != last + 1) {
+ mbb.position((HEADER_SIZE) + (i << 1));
+ }
+ last = i;
+ int combinedId = mbb.getChar();
+ BaseBlock block = FaweCache.CACHE_BLOCK[combinedId];
+ if (block.canStoreNBTData() && !nbtMap.isEmpty()) {
+ CompoundTag nbt;
+ if (nbtMap.size() < 4) {
+ nbt = null;
+ for (Map.Entry entry : nbtMap.entrySet()) {
+ IntegerTrio key = entry.getKey();
+ int index = getIndex(key.x, key.y, key.z);
+ if (index == i) {
+ nbt = entry.getValue();
+ break;
+ }
+ }
+ } else {
+ // x + z * width + y * area;
+ int y = i / area;
+ int newI = (i - (y * area));
+ int z = newI / width;
+ int x = newI - z * width;
+ nbt = nbtMap.get(new IntegerTrio(x, y, z));
+ }
+ if (nbt != null) {
+ block = new BaseBlock(block.getId(), block.getData());
+ block.setNbtData(nbt);
+ }
+ }
+ return block;
+ } catch (Exception e) {
+ MainUtil.handleError(e);
+ }
+ return EditSession.nullBlock;
+ }
+
@Override
public boolean setTile(int x, int y, int z, CompoundTag tag) {
nbtMap.put(new IntegerTrio(x, y, z), tag);
diff --git a/core/src/main/java/com/boydti/fawe/object/clipboard/FaweClipboard.java b/core/src/main/java/com/boydti/fawe/object/clipboard/FaweClipboard.java
index 9b5730c8..5e3fe345 100644
--- a/core/src/main/java/com/boydti/fawe/object/clipboard/FaweClipboard.java
+++ b/core/src/main/java/com/boydti/fawe/object/clipboard/FaweClipboard.java
@@ -24,6 +24,8 @@ public abstract class FaweClipboard {
public abstract boolean setBlock(int x, int y, int z, BaseBlock block);
+ public abstract BaseBlock getBlock(int index);
+
public abstract void setId(int index, int id);
public abstract void setData(int index, int data);
diff --git a/core/src/main/java/com/boydti/fawe/object/clipboard/MemoryOptimizedClipboard.java b/core/src/main/java/com/boydti/fawe/object/clipboard/MemoryOptimizedClipboard.java
index 64390184..dd522296 100644
--- a/core/src/main/java/com/boydti/fawe/object/clipboard/MemoryOptimizedClipboard.java
+++ b/core/src/main/java/com/boydti/fawe/object/clipboard/MemoryOptimizedClipboard.java
@@ -351,6 +351,7 @@ public class MemoryOptimizedClipboard extends FaweClipboard {
return getBlock(index);
}
+ @Override
public BaseBlock getBlock(int index) {
int id = getId(index);
if (add != null) {
diff --git a/core/src/main/java/com/boydti/fawe/object/clipboard/ReadOnlyClipboard.java b/core/src/main/java/com/boydti/fawe/object/clipboard/ReadOnlyClipboard.java
index 31c4d16f..9db2d05f 100644
--- a/core/src/main/java/com/boydti/fawe/object/clipboard/ReadOnlyClipboard.java
+++ b/core/src/main/java/com/boydti/fawe/object/clipboard/ReadOnlyClipboard.java
@@ -35,6 +35,11 @@ public abstract class ReadOnlyClipboard extends FaweClipboard {
throw new UnsupportedOperationException("Clipboard is immutable");
}
+ @Override
+ public BaseBlock getBlock(int index) {
+ throw new UnsupportedOperationException("World based clipboards do not provide index access");
+ }
+
@Override
public abstract BaseBlock getBlock(int x, int y, int z);
diff --git a/core/src/main/java/com/boydti/fawe/object/clipboard/RemappedClipboard.java b/core/src/main/java/com/boydti/fawe/object/clipboard/RemappedClipboard.java
new file mode 100644
index 00000000..04ec1a03
--- /dev/null
+++ b/core/src/main/java/com/boydti/fawe/object/clipboard/RemappedClipboard.java
@@ -0,0 +1,62 @@
+package com.boydti.fawe.object.clipboard;
+
+import com.boydti.fawe.jnbt.NBTStreamer;
+import com.sk89q.worldedit.blocks.BaseBlock;
+
+public class RemappedClipboard extends AbstractDelegateFaweClipboard {
+ private final ClipboardRemapper remapper;
+
+ public RemappedClipboard(FaweClipboard parent, ClipboardRemapper remapper) {
+ super(parent);
+ this.remapper = remapper;
+ }
+
+ @Override
+ public BaseBlock getBlock(int x, int y, int z) {
+ return remapper.remap(super.getBlock(x, y, z));
+ }
+
+ @Override
+ public BaseBlock getBlock(int index) {
+ return remapper.remap(super.getBlock(index));
+ }
+
+ @Override
+ public void forEach(BlockReader task, boolean air) {
+ super.forEach(new BlockReader() {
+ @Override
+ public void run(int x, int y, int z, BaseBlock block) {
+ task.run(x, y, z, remapper.remap(block));
+ }
+ }, air);
+ }
+
+ @Override
+ public void streamIds(NBTStreamer.ByteReader task) {
+ super.streamIds(new NBTStreamer.ByteReader() {
+ @Override
+ public void run(int index, int byteValue) {
+ if (remapper.hasRemapId(byteValue)) {
+ int result = remapper.remapId(byteValue);
+ if (result != byteValue) {
+ task.run(index, result);
+ } else {
+ task.run(index, getBlock(index).getId());
+ }
+ }
+ }
+ });
+ }
+
+ @Override
+ public void streamDatas(NBTStreamer.ByteReader task) {
+ super.streamDatas(new NBTStreamer.ByteReader() {
+ @Override
+ public void run(int index, int byteValue) {
+ if (remapper.hasRemapData(byteValue)) {
+ task.run(index, getBlock(index).getData());
+ }
+ }
+ });
+ }
+}
diff --git a/core/src/main/java/com/boydti/fawe/object/clipboard/WorldCopyClipboard.java b/core/src/main/java/com/boydti/fawe/object/clipboard/WorldCopyClipboard.java
index de5164fa..99df9297 100644
--- a/core/src/main/java/com/boydti/fawe/object/clipboard/WorldCopyClipboard.java
+++ b/core/src/main/java/com/boydti/fawe/object/clipboard/WorldCopyClipboard.java
@@ -111,7 +111,7 @@ public class WorldCopyClipboard extends ReadOnlyClipboard {
int xx = pos.getBlockX() - mx;
if (region.contains(pos)) {
BaseBlock block = getBlockAbs(x, y, z);
- if (!air && block == EditSession.nullBlock) {
+ if (!air && block.getId() == 0) {
continue;
}
CompoundTag tag = block.getNbtData();
diff --git a/core/src/main/java/com/boydti/fawe/object/collection/ByteStore.java b/core/src/main/java/com/boydti/fawe/object/collection/ByteStore.java
new file mode 100644
index 00000000..81671782
--- /dev/null
+++ b/core/src/main/java/com/boydti/fawe/object/collection/ByteStore.java
@@ -0,0 +1,14 @@
+package com.boydti.fawe.object.collection;
+
+public class ByteStore extends IterableThreadLocal {
+ private final int size;
+
+ public ByteStore(int size) {
+ this.size = size;
+ }
+
+ @Override
+ public byte[] init() {
+ return new byte[size];
+ }
+}
diff --git a/core/src/main/java/com/boydti/fawe/object/io/LittleEndianOutputStream.java b/core/src/main/java/com/boydti/fawe/object/io/LittleEndianOutputStream.java
new file mode 100644
index 00000000..bc9060ce
--- /dev/null
+++ b/core/src/main/java/com/boydti/fawe/object/io/LittleEndianOutputStream.java
@@ -0,0 +1,280 @@
+package com.boydti.fawe.object.io;
+
+import java.io.DataOutput;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UTFDataFormatException;
+
+public class LittleEndianOutputStream extends FilterOutputStream implements DataOutput {
+
+ /**
+ * The number of bytes written so far to the little endian output stream.
+ */
+ protected int written;
+
+ /**
+ * Creates a new little endian output stream and chains it to the
+ * output stream specified by the out argument.
+ *
+ * @param out the underlying output stream.
+ * @see java.io.FilterOutputStream#out
+ */
+ public LittleEndianOutputStream(OutputStream out) {
+ super(out);
+ }
+
+ /**
+ * Writes the specified byte value to the underlying output stream.
+ *
+ * @param b the byte
value to be written.
+ * @exception IOException if the underlying stream throws an IOException.
+ */
+ public synchronized void write(int b) throws IOException {
+ out.write(b);
+ written++;
+ }
+
+ /**
+ * Writes length
bytes from the specified byte array
+ * starting at offset
to the underlying output stream.
+ *
+ * @param data the data.
+ * @param offset the start offset in the data.
+ * @param length the number of bytes to write.
+ * @exception IOException if the underlying stream throws an IOException.
+ */
+ public synchronized void write(byte[] data, int offset, int length)
+ throws IOException {
+ out.write(data, offset, length);
+ written += length;
+ }
+
+
+ /**
+ * Writes a boolean
to the underlying output stream as
+ * a single byte. If the argument is true, the byte value 1 is written.
+ * If the argument is false, the byte value 0
in written.
+ *
+ * @param b the boolean
value to be written.
+ * @exception IOException if the underlying stream throws an IOException.
+ */
+ public void writeBoolean(boolean b) throws IOException {
+
+ if (b) this.write(1);
+ else this.write(0);
+
+ }
+
+ /**
+ * Writes out a byte
to the underlying output stream
+ *
+ * @param b the byte
value to be written.
+ * @exception IOException if the underlying stream throws an IOException.
+ */
+ public void writeByte(int b) throws IOException {
+ out.write(b);
+ written++;
+ }
+
+ /**
+ * Writes a two byte short
to the underlying output stream in
+ * little endian order, low byte first.
+ *
+ * @param s the short
to be written.
+ * @exception IOException if the underlying stream throws an IOException.
+ */
+ public void writeShort(int s) throws IOException {
+
+ out.write(s & 0xFF);
+ out.write((s >>> 8) & 0xFF);
+ written += 2;
+
+ }
+
+ /**
+ * Writes a two byte char
to the underlying output stream
+ * in little endian order, low byte first.
+ *
+ * @param c the char
value to be written.
+ * @exception IOException if the underlying stream throws an IOException.
+ */
+ public void writeChar(int c) throws IOException {
+
+ out.write(c & 0xFF);
+ out.write((c >>> 8) & 0xFF);
+ written += 2;
+
+ }
+
+ /**
+ * Writes a four-byte int
to the underlying output stream
+ * in little endian order, low byte first, high byte last
+ *
+ * @param i the int
to be written.
+ * @exception IOException if the underlying stream throws an IOException.
+ */
+ public void writeInt(int i) throws IOException {
+
+ out.write(i & 0xFF);
+ out.write((i >>> 8) & 0xFF);
+ out.write((i >>> 16) & 0xFF);
+ out.write((i >>> 24) & 0xFF);
+ written += 4;
+
+ }
+
+ /**
+ * Writes an eight-byte long
to the underlying output stream
+ * in little endian order, low byte first, high byte last
+ *
+ * @param l the long
to be written.
+ * @exception IOException if the underlying stream throws an IOException.
+ */
+ public void writeLong(long l) throws IOException {
+
+ out.write((int) l & 0xFF);
+ out.write((int) (l >>> 8) & 0xFF);
+ out.write((int) (l >>> 16) & 0xFF);
+ out.write((int) (l >>> 24) & 0xFF);
+ out.write((int) (l >>> 32) & 0xFF);
+ out.write((int) (l >>> 40) & 0xFF);
+ out.write((int) (l >>> 48) & 0xFF);
+ out.write((int) (l >>> 56) & 0xFF);
+ written += 8;
+
+ }
+
+ /**
+ * Writes a 4 byte Java float to the underlying output stream in
+ * little endian order.
+ *
+ * @param f the float
value to be written.
+ * @exception IOException if an I/O error occurs.
+ */
+ public final void writeFloat(float f) throws IOException {
+
+ this.writeInt(Float.floatToIntBits(f));
+
+ }
+
+ /**
+ * Writes an 8 byte Java double to the underlying output stream in
+ * little endian order.
+ *
+ * @param d the double
value to be written.
+ * @exception IOException if an I/O error occurs.
+ */
+ public final void writeDouble(double d) throws IOException {
+
+ this.writeLong(Double.doubleToLongBits(d));
+
+ }
+
+ /**
+ * Writes a string to the underlying output stream as a sequence of
+ * bytes. Each character is written to the data output stream as
+ * if by the writeByte()
method.
+ *
+ * @param s the String
value to be written.
+ * @exception IOException if the underlying stream throws an IOException.
+ * @see java.io.DataOutputStream#writeByte(int)
+ * @see java.io.DataOutputStream#out
+ */
+ public void writeBytes(String s) throws IOException {
+
+ int length = s.length();
+ for (int i = 0; i < length; i++) {
+ out.write((byte) s.charAt(i));
+ }
+ written += length;
+ }
+
+ /**
+ * Writes a string to the underlying output stream as a sequence of
+ * characters. Each character is written to the data output stream as
+ * if by the writeChar
method.
+ *
+ * @param s a String
value to be written.
+ * @exception IOException if the underlying stream throws an IOException.
+ * @see java.io.DataOutputStream#writeChar(int)
+ * @see java.io.DataOutputStream#out
+ */
+ public void writeChars(String s) throws IOException {
+
+ int length = s.length();
+ for (int i = 0; i < length; i++) {
+ int c = s.charAt(i);
+ out.write(c & 0xFF);
+ out.write((c >>> 8) & 0xFF);
+ }
+ written += length * 2;
+
+ }
+
+ /**
+ * Writes a string of no more than 65,535 characters
+ * to the underlying output stream using UTF-8
+ * encoding. This method first writes a two byte short
+ * in big endian order as required by the
+ * UTF-8 specification. This gives the number of bytes in the
+ * UTF-8 encoded version of the string, not the number of characters
+ * in the string. Next each character of the string is written
+ * using the UTF-8 encoding for the character.
+ *
+ * @param s the string to be written.
+ * @exception UTFDataFormatException if the string is longer than
+ * 65,535 characters.
+ * @exception IOException if the underlying stream throws an IOException.
+ */
+ public void writeUTF(String s) throws IOException {
+
+ int numchars = s.length();
+ int numbytes = 0;
+
+ for (int i = 0 ; i < numchars ; i++) {
+ int c = s.charAt(i);
+ if ((c >= 0x0001) && (c <= 0x007F)) numbytes++;
+ else if (c > 0x07FF) numbytes += 3;
+ else numbytes += 2;
+ }
+
+ if (numbytes > 65535) throw new UTFDataFormatException();
+
+ out.write((numbytes >>> 8) & 0xFF);
+ out.write(numbytes & 0xFF);
+ for (int i = 0 ; i < numchars ; i++) {
+ int c = s.charAt(i);
+ if ((c >= 0x0001) && (c <= 0x007F)) {
+ out.write(c);
+ }
+ else if (c > 0x07FF) {
+ out.write(0xE0 | ((c >> 12) & 0x0F));
+ out.write(0x80 | ((c >> 6) & 0x3F));
+ out.write(0x80 | (c & 0x3F));
+ written += 2;
+ }
+ else {
+ out.write(0xC0 | ((c >> 6) & 0x1F));
+ out.write(0x80 | (c & 0x3F));
+ written += 1;
+ }
+ }
+
+ written += numchars + 2;
+
+ }
+
+ /**
+ * Returns the number of bytes written to this little endian output stream.
+ * (This class is not thread-safe with respect to this method. It is
+ * possible that this number is temporarily less than the actual
+ * number of bytes written.)
+ * @return the value of the written
field.
+ * @see java.io.DataOutputStream#written
+ */
+ public int size() {
+ return this.written;
+ }
+
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/boydti/fawe/object/io/PGZIPOutputStream.java b/core/src/main/java/com/boydti/fawe/object/io/PGZIPOutputStream.java
index ef36b112..2eedcd14 100644
--- a/core/src/main/java/com/boydti/fawe/object/io/PGZIPOutputStream.java
+++ b/core/src/main/java/com/boydti/fawe/object/io/PGZIPOutputStream.java
@@ -41,8 +41,8 @@ public class PGZIPOutputStream extends FilterOutputStream {
// array list that contains the block sizes
ArrayList blockSizes = new ArrayList();
- private int level = Deflater.BEST_SPEED;
- private int strategy = Deflater.HUFFMAN_ONLY;
+ private int level = Deflater.DEFAULT_COMPRESSION;
+ private int strategy = Deflater.DEFAULT_STRATEGY;
@Nonnull
protected Deflater newDeflater() {
diff --git a/core/src/main/java/com/boydti/fawe/object/io/zstd/BitStream.java b/core/src/main/java/com/boydti/fawe/object/io/zstd/BitStream.java
new file mode 100644
index 00000000..6957593f
--- /dev/null
+++ b/core/src/main/java/com/boydti/fawe/object/io/zstd/BitStream.java
@@ -0,0 +1,206 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.boydti.fawe.object.io.zstd;
+
+import static com.boydti.fawe.object.io.zstd.UnsafeUtil.UNSAFE;
+import static com.boydti.fawe.object.io.zstd.Util.highestBit;
+import static com.boydti.fawe.object.io.zstd.Util.verify;
+import static com.boydti.fawe.object.io.zstd.ZstdFrameDecompressor.SIZE_OF_LONG;
+
+/**
+ * Bit streams are encoded as a byte-aligned little-endian stream. Thus, bits are laid out
+ * in the following manner, and the stream is read from right to left.
+ *
+ *
+ * ... [16 17 18 19 20 21 22 23] [8 9 10 11 12 13 14 15] [0 1 2 3 4 5 6 7]
+ */
+class BitStream
+{
+ private BitStream()
+ {
+ }
+
+ public static boolean isEndOfStream(long startAddress, long currentAddress, int bitsConsumed)
+ {
+ return startAddress == currentAddress && bitsConsumed == Long.SIZE;
+ }
+
+ static long readTail(Object inputBase, long inputAddress, int inputSize)
+ {
+ long bits = UNSAFE.getByte(inputBase, inputAddress) & 0xFF;
+
+ switch (inputSize) {
+ case 7:
+ bits |= (UNSAFE.getByte(inputBase, inputAddress + 6) & 0xFFL) << 48;
+ case 6:
+ bits |= (UNSAFE.getByte(inputBase, inputAddress + 5) & 0xFFL) << 40;
+ case 5:
+ bits |= (UNSAFE.getByte(inputBase, inputAddress + 4) & 0xFFL) << 32;
+ case 4:
+ bits |= (UNSAFE.getByte(inputBase, inputAddress + 3) & 0xFFL) << 24;
+ case 3:
+ bits |= (UNSAFE.getByte(inputBase, inputAddress + 2) & 0xFFL) << 16;
+ case 2:
+ bits |= (UNSAFE.getByte(inputBase, inputAddress + 1) & 0xFFL) << 8;
+ }
+
+ return bits;
+ }
+
+ /**
+ * @return numberOfBits in the low order bits of a long
+ */
+ public static long peekBits(int bitsConsumed, long bitContainer, int numberOfBits)
+ {
+ return (((bitContainer << bitsConsumed) >>> 1) >>> (63 - numberOfBits));
+ }
+
+ /**
+ * numberOfBits must be > 0
+ *
+ * @return numberOfBits in the low order bits of a long
+ */
+ public static long peekBitsFast(int bitsConsumed, long bitContainer, int numberOfBits)
+ {
+ return ((bitContainer << bitsConsumed) >>> (64 - numberOfBits));
+ }
+
+ static class Initializer
+ {
+ private final Object inputBase;
+ private final long startAddress;
+ private final long endAddress;
+ private long bits;
+ private long currentAddress;
+ private int bitsConsumed;
+
+ public Initializer(Object inputBase, long startAddress, long endAddress)
+ {
+ this.inputBase = inputBase;
+ this.startAddress = startAddress;
+ this.endAddress = endAddress;
+ }
+
+ public long getBits()
+ {
+ return bits;
+ }
+
+ public long getCurrentAddress()
+ {
+ return currentAddress;
+ }
+
+ public int getBitsConsumed()
+ {
+ return bitsConsumed;
+ }
+
+ public void initialize()
+ {
+ verify(endAddress - startAddress >= 1, startAddress, "Bitstream is empty");
+
+ int lastByte = UNSAFE.getByte(inputBase, endAddress - 1) & 0xFF;
+ verify(lastByte != 0, endAddress, "Bitstream end mark not present");
+
+ bitsConsumed = SIZE_OF_LONG - highestBit(lastByte);
+
+ int inputSize = (int) (endAddress - startAddress);
+ if (inputSize >= SIZE_OF_LONG) { /* normal case */
+ currentAddress = endAddress - SIZE_OF_LONG;
+ bits = UNSAFE.getLong(inputBase, currentAddress);
+ }
+ else {
+ currentAddress = startAddress;
+ bits = readTail(inputBase, startAddress, inputSize);
+
+ bitsConsumed += (SIZE_OF_LONG - inputSize) * 8;
+ }
+ }
+ }
+
+ static final class Loader
+ {
+ private final Object inputBase;
+ private final long startAddress;
+ private long bits;
+ private long currentAddress;
+ private int bitsConsumed;
+ private boolean overflow;
+
+ public Loader(Object inputBase, long startAddress, long currentAddress, long bits, int bitsConsumed)
+ {
+ this.inputBase = inputBase;
+ this.startAddress = startAddress;
+ this.bits = bits;
+ this.currentAddress = currentAddress;
+ this.bitsConsumed = bitsConsumed;
+ }
+
+ public long getBits()
+ {
+ return bits;
+ }
+
+ public long getCurrentAddress()
+ {
+ return currentAddress;
+ }
+
+ public int getBitsConsumed()
+ {
+ return bitsConsumed;
+ }
+
+ public boolean isOverflow()
+ {
+ return overflow;
+ }
+
+ public boolean load()
+ {
+ if (bitsConsumed > 64) {
+ overflow = true;
+ return true;
+ }
+
+ else if (currentAddress == startAddress) {
+ return true;
+ }
+
+ int bytes = bitsConsumed >>> 3; // divide by 8
+ if (currentAddress >= startAddress + SIZE_OF_LONG) {
+ if (bytes > 0) {
+ currentAddress -= bytes;
+ bits = UNSAFE.getLong(inputBase, currentAddress);
+ }
+ bitsConsumed &= 0b111;
+ }
+ else if (currentAddress - bytes < startAddress) {
+ bytes = (int) (currentAddress - startAddress);
+ currentAddress = startAddress;
+ bitsConsumed -= bytes * SIZE_OF_LONG;
+ bits = UNSAFE.getLong(inputBase, startAddress);
+ return true;
+ }
+ else {
+ currentAddress -= bytes;
+ bitsConsumed -= bytes * SIZE_OF_LONG;
+ bits = UNSAFE.getLong(inputBase, currentAddress);
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/core/src/main/java/com/boydti/fawe/object/io/zstd/FiniteStateEntropy.java b/core/src/main/java/com/boydti/fawe/object/io/zstd/FiniteStateEntropy.java
new file mode 100644
index 00000000..9569b3db
--- /dev/null
+++ b/core/src/main/java/com/boydti/fawe/object/io/zstd/FiniteStateEntropy.java
@@ -0,0 +1,176 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.boydti.fawe.object.io.zstd;
+
+import static com.boydti.fawe.object.io.zstd.BitStream.peekBits;
+import static com.boydti.fawe.object.io.zstd.FseTableReader.FSE_MAX_SYMBOL_VALUE;
+import static com.boydti.fawe.object.io.zstd.UnsafeUtil.UNSAFE;
+import static sun.misc.Unsafe.ARRAY_BYTE_BASE_OFFSET;
+
+class FiniteStateEntropy
+{
+ private static final int MAX_TABLE_LOG = 12;
+
+ private final FiniteStateEntropy.Table table;
+ private final FseTableReader reader = new FseTableReader();
+
+ public FiniteStateEntropy(int maxLog)
+ {
+ table = new FiniteStateEntropy.Table(maxLog);
+ }
+
+ public int decompress(final Object inputBase, final long inputAddress, final long inputLimit, byte[] weights)
+ {
+ long input = inputAddress;
+ input += reader.readFseTable(table, inputBase, input, inputLimit, FSE_MAX_SYMBOL_VALUE, MAX_TABLE_LOG);
+
+ final Object outputBase = weights;
+ final long outputAddress = ARRAY_BYTE_BASE_OFFSET;
+ final long outputLimit = outputAddress + weights.length;
+
+ long output = outputAddress;
+
+ // initialize bit stream
+ BitStream.Initializer initializer = new BitStream.Initializer(inputBase, input, inputLimit);
+ initializer.initialize();
+ int bitsConsumed = initializer.getBitsConsumed();
+ long currentAddress = initializer.getCurrentAddress();
+ long bits = initializer.getBits();
+
+ // initialize first FSE stream
+ int state1 = (int) peekBits(bitsConsumed, bits, table.log2Size);
+ bitsConsumed += table.log2Size;
+
+ BitStream.Loader loader = new BitStream.Loader(inputBase, input, currentAddress, bits, bitsConsumed);
+ loader.load();
+ bits = loader.getBits();
+ bitsConsumed = loader.getBitsConsumed();
+ currentAddress = loader.getCurrentAddress();
+
+ // initialize second FSE stream
+ int state2 = (int) peekBits(bitsConsumed, bits, table.log2Size);
+ bitsConsumed += table.log2Size;
+
+ loader = new BitStream.Loader(inputBase, input, currentAddress, bits, bitsConsumed);
+ loader.load();
+ bits = loader.getBits();
+ bitsConsumed = loader.getBitsConsumed();
+ currentAddress = loader.getCurrentAddress();
+
+ byte[] symbols = table.symbol;
+ byte[] numbersOfBits = table.numberOfBits;
+ int[] newStates = table.newState;
+
+ // decode 4 symbols per loop
+ while (output < outputLimit) {
+ int numberOfBits;
+
+ UNSAFE.putByte(outputBase, output, symbols[state1]);
+ numberOfBits = numbersOfBits[state1];
+ state1 = (int) (newStates[state1] + peekBits(bitsConsumed, bits, numberOfBits));
+ bitsConsumed += numberOfBits;
+
+ UNSAFE.putByte(outputBase, output + 1, symbols[state2]);
+ numberOfBits = numbersOfBits[state2];
+ state2 = (int) (newStates[state2] + peekBits(bitsConsumed, bits, numberOfBits));
+ bitsConsumed += numberOfBits;
+
+ UNSAFE.putByte(outputBase, output + 2, symbols[state1]);
+ numberOfBits = numbersOfBits[state1];
+ state1 = (int) (newStates[state1] + peekBits(bitsConsumed, bits, numberOfBits));
+ bitsConsumed += numberOfBits;
+
+ UNSAFE.putByte(outputBase, output + 3, symbols[state2]);
+ numberOfBits = numbersOfBits[state2];
+ state2 = (int) (newStates[state2] + peekBits(bitsConsumed, bits, numberOfBits));
+ bitsConsumed += numberOfBits;
+
+ output += ZstdFrameDecompressor.SIZE_OF_INT;
+
+ loader = new BitStream.Loader(inputBase, input, currentAddress, bits, bitsConsumed);
+ boolean done = loader.load();
+ bitsConsumed = loader.getBitsConsumed();
+ bits = loader.getBits();
+ currentAddress = loader.getCurrentAddress();
+ if (done) {
+ break;
+ }
+ }
+
+ while (true) {
+ UNSAFE.putByte(outputBase, output++, symbols[state1]);
+ int numberOfBits = numbersOfBits[state1];
+ state1 = (int) (newStates[state1] + peekBits(bitsConsumed, bits, numberOfBits));
+ bitsConsumed += numberOfBits;
+
+ loader = new BitStream.Loader(inputBase, input, currentAddress, bits, bitsConsumed);
+ loader.load();
+ bitsConsumed = loader.getBitsConsumed();
+ bits = loader.getBits();
+ currentAddress = loader.getCurrentAddress();
+
+ if (loader.isOverflow()) {
+ UNSAFE.putByte(outputBase, output++, symbols[state2]);
+ break;
+ }
+
+ UNSAFE.putByte(outputBase, output++, symbols[state2]);
+ int numberOfBits1 = numbersOfBits[state2];
+ state2 = (int) (newStates[state2] + peekBits(bitsConsumed, bits, numberOfBits1));
+ bitsConsumed += numberOfBits1;
+
+ loader = new BitStream.Loader(inputBase, input, currentAddress, bits, bitsConsumed);
+ loader.load();
+ bitsConsumed = loader.getBitsConsumed();
+ bits = loader.getBits();
+ currentAddress = loader.getCurrentAddress();
+
+ if (loader.isOverflow()) {
+ UNSAFE.putByte(outputBase, output++, symbols[state1]);
+ break;
+ }
+ }
+
+ return (int) (output - outputAddress);
+ }
+
+ public static final class Table
+ {
+ int log2Size;
+ final int[] newState;
+ final byte[] symbol;
+ final byte[] numberOfBits;
+
+ public Table(int log2Size)
+ {
+ int size = 1 << log2Size;
+ newState = new int[size];
+ symbol = new byte[size];
+ numberOfBits = new byte[size];
+ }
+
+ public Table(int log2Size, int[] newState, byte[] symbol, byte[] numberOfBits)
+ {
+ int size = 1 << log2Size;
+ if (newState.length != size || symbol.length != size || numberOfBits.length != size) {
+ throw new IllegalArgumentException("Expected arrays to match provided size");
+ }
+
+ this.log2Size = log2Size;
+ this.newState = newState;
+ this.symbol = symbol;
+ this.numberOfBits = numberOfBits;
+ }
+ }
+}
diff --git a/core/src/main/java/com/boydti/fawe/object/io/zstd/FrameHeader.java b/core/src/main/java/com/boydti/fawe/object/io/zstd/FrameHeader.java
new file mode 100644
index 00000000..f310ea51
--- /dev/null
+++ b/core/src/main/java/com/boydti/fawe/object/io/zstd/FrameHeader.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.boydti.fawe.object.io.zstd;
+
+class FrameHeader
+{
+ final long headerSize;
+ final int windowSize;
+ final long contentSize;
+ final long dictionaryId;
+ final boolean hasChecksum;
+
+ public FrameHeader(long headerSize, int windowSize, long contentSize, long dictionaryId, boolean hasChecksum)
+ {
+ this.headerSize = headerSize;
+ this.windowSize = windowSize;
+ this.contentSize = contentSize;
+ this.dictionaryId = dictionaryId;
+ this.hasChecksum = hasChecksum;
+ }
+}
diff --git a/core/src/main/java/com/boydti/fawe/object/io/zstd/FseTableReader.java b/core/src/main/java/com/boydti/fawe/object/io/zstd/FseTableReader.java
new file mode 100644
index 00000000..bce65eac
--- /dev/null
+++ b/core/src/main/java/com/boydti/fawe/object/io/zstd/FseTableReader.java
@@ -0,0 +1,182 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.boydti.fawe.object.io.zstd;
+
+import static com.boydti.fawe.object.io.zstd.UnsafeUtil.UNSAFE;
+import static com.boydti.fawe.object.io.zstd.Util.highestBit;
+import static com.boydti.fawe.object.io.zstd.Util.verify;
+
+class FseTableReader
+{
+ private static final int FSE_MIN_TABLE_LOG = 5;
+
+ public static final int FSE_MAX_SYMBOL_VALUE = 255;
+ private final short[] nextSymbol = new short[FSE_MAX_SYMBOL_VALUE + 1];
+ private final short[] normalizedCounters = new short[FSE_MAX_SYMBOL_VALUE + 1];
+
+ public int readFseTable(FiniteStateEntropy.Table table, Object inputBase, long inputAddress, long inputLimit, int maxSymbol, int maxTableLog)
+ {
+ // read table headers
+ long input = inputAddress;
+ verify(inputLimit - inputAddress >= 4, input, "Not enough input bytes");
+
+ int threshold;
+ int symbolNumber = 0;
+ boolean previousIsZero = false;
+
+ int bitStream = UNSAFE.getInt(inputBase, input);
+
+ int tableLog = (bitStream & 0xF) + FSE_MIN_TABLE_LOG;
+
+ int numberOfBits = tableLog + 1;
+ bitStream >>>= 4;
+ int bitCount = 4;
+
+ verify(tableLog <= maxTableLog, input, "FSE table size exceeds maximum allowed size");
+
+ int remaining = (1 << tableLog) + 1;
+ threshold = 1 << tableLog;
+
+ while (remaining > 1 && symbolNumber <= maxSymbol) {
+ if (previousIsZero) {
+ int n0 = symbolNumber;
+ while ((bitStream & 0xFFFF) == 0xFFFF) {
+ n0 += 24;
+ if (input < inputLimit - 5) {
+ input += 2;
+ bitStream = (UNSAFE.getInt(inputBase, input) >>> bitCount);
+ }
+ else {
+ // end of bit stream
+ bitStream >>>= 16;
+ bitCount += 16;
+ }
+ }
+ while ((bitStream & 3) == 3) {
+ n0 += 3;
+ bitStream >>>= 2;
+ bitCount += 2;
+ }
+ n0 += bitStream & 3;
+ bitCount += 2;
+
+ verify(n0 <= maxSymbol, input, "Symbol larger than max value");
+
+ while (symbolNumber < n0) {
+ normalizedCounters[symbolNumber++] = 0;
+ }
+ if ((input <= inputLimit - 7) || (input + (bitCount >>> 3) <= inputLimit - 4)) {
+ input += bitCount >>> 3;
+ bitCount &= 7;
+ bitStream = UNSAFE.getInt(inputBase, input) >>> bitCount;
+ }
+ else {
+ bitStream >>>= 2;
+ }
+ }
+
+ short max = (short) ((2 * threshold - 1) - remaining);
+ short count;
+
+ if ((bitStream & (threshold - 1)) < max) {
+ count = (short) (bitStream & (threshold - 1));
+ bitCount += numberOfBits - 1;
+ }
+ else {
+ count = (short) (bitStream & (2 * threshold - 1));
+ if (count >= threshold) {
+ count -= max;
+ }
+ bitCount += numberOfBits;
+ }
+ count--; // extra accuracy
+
+ remaining -= Math.abs(count);
+ normalizedCounters[symbolNumber++] = count;
+ previousIsZero = count == 0;
+ while (remaining < threshold) {
+ numberOfBits--;
+ threshold >>>= 1;
+ }
+
+ if ((input <= inputLimit - 7) || (input + (bitCount >> 3) <= inputLimit - 4)) {
+ input += bitCount >>> 3;
+ bitCount &= 7;
+ }
+ else {
+ bitCount -= (int) (8 * (inputLimit - 4 - input));
+ input = inputLimit - 4;
+ }
+ bitStream = UNSAFE.getInt(inputBase, input) >>> (bitCount & 31);
+ }
+
+ verify(remaining == 1 && bitCount <= 32, input, "Input is corrupted");
+
+ maxSymbol = symbolNumber - 1;
+ verify(maxSymbol <= FSE_MAX_SYMBOL_VALUE, input, "Max symbol value too large (too many symbols for FSE)");
+
+ input += (bitCount + 7) >> 3;
+
+ // populate decoding table
+ int symbolCount = maxSymbol + 1;
+ int tableSize = 1 << tableLog;
+ int highThreshold = tableSize - 1;
+
+ table.log2Size = tableLog;
+
+ for (byte symbol = 0; symbol < symbolCount; symbol++) {
+ if (normalizedCounters[symbol] == -1) {
+ table.symbol[highThreshold--] = symbol;
+ nextSymbol[symbol] = 1;
+ }
+ else {
+ nextSymbol[symbol] = normalizedCounters[symbol];
+ }
+ }
+
+ // spread symbols
+ int tableMask = tableSize - 1;
+ int step = (tableSize >>> 1) + (tableSize >>> 3) + 3;
+ int position = 0;
+ for (byte symbol = 0; symbol < symbolCount; symbol++) {
+ for (int i = 0; i < normalizedCounters[symbol]; i++) {
+ table.symbol[position] = symbol;
+ do {
+ position = (position + step) & tableMask;
+ }
+ while (position > highThreshold);
+ }
+ }
+
+ // position must reach all cells once, otherwise normalizedCounter is incorrect
+ verify(position == 0, input, "Input is corrupted");
+
+ for (int i = 0; i < tableSize; i++) {
+ byte symbol = table.symbol[i];
+ short nextState = nextSymbol[symbol]++;
+ table.numberOfBits[i] = (byte) (tableLog - highestBit(nextState));
+ table.newState[i] = (short) ((nextState << table.numberOfBits[i]) - tableSize);
+ }
+
+ return (int) (input - inputAddress);
+ }
+
+ public static void buildRleTable(FiniteStateEntropy.Table table, byte value)
+ {
+ table.log2Size = 0;
+ table.symbol[0] = value;
+ table.newState[0] = 0;
+ table.numberOfBits[0] = 0;
+ }
+}
diff --git a/core/src/main/java/com/boydti/fawe/object/io/zstd/Huffman.java b/core/src/main/java/com/boydti/fawe/object/io/zstd/Huffman.java
new file mode 100644
index 00000000..990de496
--- /dev/null
+++ b/core/src/main/java/com/boydti/fawe/object/io/zstd/Huffman.java
@@ -0,0 +1,317 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.boydti.fawe.object.io.zstd;
+
+import java.util.Arrays;
+
+
+import static com.boydti.fawe.object.io.zstd.BitStream.isEndOfStream;
+import static com.boydti.fawe.object.io.zstd.BitStream.peekBitsFast;
+import static com.boydti.fawe.object.io.zstd.UnsafeUtil.UNSAFE;
+import static com.boydti.fawe.object.io.zstd.Util.isPowerOf2;
+import static com.boydti.fawe.object.io.zstd.Util.verify;
+import static com.boydti.fawe.object.io.zstd.ZstdFrameDecompressor.SIZE_OF_INT;
+import static com.boydti.fawe.object.io.zstd.ZstdFrameDecompressor.SIZE_OF_SHORT;
+
+class Huffman
+{
+ private static final int MAX_SYMBOL = 255;
+ private static final int MAX_TABLE_LOG = 12;
+
+ // stats
+ private final byte[] weights = new byte[MAX_SYMBOL + 1];
+ private final int[] ranks = new int[MAX_TABLE_LOG + 1];
+
+ // table
+ private int tableLog = -1;
+ private final byte[] symbols = new byte[1 << MAX_TABLE_LOG];
+ private final byte[] numbersOfBits = new byte[1 << MAX_TABLE_LOG];
+
+ private final FiniteStateEntropy finiteStateEntropy = new FiniteStateEntropy(6);
+
+ public boolean isLoaded()
+ {
+ return tableLog != -1;
+ }
+
+ public int readTable(final Object inputBase, final long inputAddress, final int size)
+ {
+ Arrays.fill(ranks, 0);
+ long input = inputAddress;
+
+ // read table header
+ verify(size > 0, input, "Not enough input bytes");
+ int inputSize = UNSAFE.getByte(inputBase, input++) & 0xFF;
+
+ int outputSize;
+ if (inputSize >= 128) {
+ outputSize = inputSize - 127;
+ inputSize = ((outputSize + 1) / 2);
+
+ verify(inputSize + 1 <= size, input, "Not enough input bytes");
+ verify(outputSize <= MAX_SYMBOL + 1, input, "Input is corrupted");
+
+ for (int i = 0; i < outputSize; i += 2) {
+ int value = UNSAFE.getByte(inputBase, input + i / 2) & 0xFF;
+ weights[i] = (byte) (value >>> 4);
+ weights[i + 1] = (byte) (value & 0b1111);
+ }
+ }
+ else {
+ verify(inputSize + 1 <= size, input, "Not enough input bytes");
+
+ outputSize = finiteStateEntropy.decompress(inputBase, input, input + inputSize, weights);
+ }
+
+ int totalWeight = 0;
+ for (int i = 0; i < outputSize; i++) {
+ ranks[weights[i]]++;
+ totalWeight += (1 << weights[i]) >> 1; // TODO same as 1 << (weights[n] - 1)?
+ }
+ verify(totalWeight != 0, input, "Input is corrupted");
+
+ tableLog = Util.highestBit(totalWeight) + 1;
+ verify(tableLog <= MAX_TABLE_LOG, input, "Input is corrupted");
+
+ int total = 1 << tableLog;
+ int rest = total - totalWeight;
+ verify(isPowerOf2(rest), input, "Input is corrupted");
+
+ int lastWeight = Util.highestBit(rest) + 1;
+
+ weights[outputSize] = (byte) lastWeight;
+ ranks[lastWeight]++;
+
+ int numberOfSymbols = outputSize + 1;
+
+ // populate table
+ int nextRankStart = 0;
+ for (int i = 1; i < tableLog + 1; ++i) {
+ int current = nextRankStart;
+ nextRankStart += ranks[i] << (i - 1);
+ ranks[i] = current;
+ }
+
+ for (int n = 0; n < numberOfSymbols; n++) {
+ int weight = weights[n];
+ int length = (1 << weight) >> 1; // TODO: 1 << (weight - 1) ??
+
+ byte symbol = (byte) n;
+ byte numberOfBits = (byte) (tableLog + 1 - weight);
+ for (int i = ranks[weight]; i < ranks[weight] + length; i++) {
+ symbols[i] = symbol;
+ numbersOfBits[i] = numberOfBits;
+ }
+ ranks[weight] += length;
+ }
+
+ verify(ranks[1] >= 2 && (ranks[1] & 1) == 0, input, "Input is corrupted");
+
+ return inputSize + 1;
+ }
+
+ public void decodeSingleStream(final Object inputBase, final long inputAddress, final long inputLimit, final Object outputBase, final long outputAddress, final long outputLimit)
+ {
+ BitStream.Initializer initializer = new BitStream.Initializer(inputBase, inputAddress, inputLimit);
+ initializer.initialize();
+
+ long bits = initializer.getBits();
+ int bitsConsumed = initializer.getBitsConsumed();
+ long currentAddress = initializer.getCurrentAddress();
+
+ int tableLog = this.tableLog;
+ byte[] numbersOfBits = this.numbersOfBits;
+ byte[] symbols = this.symbols;
+
+ // 4 symbols at a time
+ long output = outputAddress;
+ long fastOutputLimit = outputLimit - 4;
+ while (output < fastOutputLimit) {
+ BitStream.Loader loader = new BitStream.Loader(inputBase, inputAddress, currentAddress, bits, bitsConsumed);
+ boolean done = loader.load();
+ bits = loader.getBits();
+ bitsConsumed = loader.getBitsConsumed();
+ currentAddress = loader.getCurrentAddress();
+ if (done) {
+ break;
+ }
+
+ bitsConsumed = decodeSymbol(outputBase, output, bits, bitsConsumed, tableLog, numbersOfBits, symbols);
+ bitsConsumed = decodeSymbol(outputBase, output + 1, bits, bitsConsumed, tableLog, numbersOfBits, symbols);
+ bitsConsumed = decodeSymbol(outputBase, output + 2, bits, bitsConsumed, tableLog, numbersOfBits, symbols);
+ bitsConsumed = decodeSymbol(outputBase, output + 3, bits, bitsConsumed, tableLog, numbersOfBits, symbols);
+ output += SIZE_OF_INT;
+ }
+
+ decodeTail(inputBase, inputAddress, currentAddress, bitsConsumed, bits, outputBase, output, outputLimit);
+ }
+
+ public void decode4Streams(final Object inputBase, final long inputAddress, final long inputLimit, final Object outputBase, final long outputAddress, final long outputLimit)
+ {
+ verify(inputLimit - inputAddress >= 10, inputAddress, "Input is corrupted"); // jump table + 1 byte per stream
+
+ long start1 = inputAddress + 3 * SIZE_OF_SHORT; // for the shorts we read below
+ long start2 = start1 + (UNSAFE.getShort(inputBase, inputAddress) & 0xFFFF);
+ long start3 = start2 + (UNSAFE.getShort(inputBase, inputAddress + 2) & 0xFFFF);
+ long start4 = start3 + (UNSAFE.getShort(inputBase, inputAddress + 4) & 0xFFFF);
+
+ BitStream.Initializer initializer = new BitStream.Initializer(inputBase, start1, start2);
+ initializer.initialize();
+ int stream1bitsConsumed = initializer.getBitsConsumed();
+ long stream1currentAddress = initializer.getCurrentAddress();
+ long stream1bits = initializer.getBits();
+
+ initializer = new BitStream.Initializer(inputBase, start2, start3);
+ initializer.initialize();
+ int stream2bitsConsumed = initializer.getBitsConsumed();
+ long stream2currentAddress = initializer.getCurrentAddress();
+ long stream2bits = initializer.getBits();
+
+ initializer = new BitStream.Initializer(inputBase, start3, start4);
+ initializer.initialize();
+ int stream3bitsConsumed = initializer.getBitsConsumed();
+ long stream3currentAddress = initializer.getCurrentAddress();
+ long stream3bits = initializer.getBits();
+
+ initializer = new BitStream.Initializer(inputBase, start4, inputLimit);
+ initializer.initialize();
+ int stream4bitsConsumed = initializer.getBitsConsumed();
+ long stream4currentAddress = initializer.getCurrentAddress();
+ long stream4bits = initializer.getBits();
+
+ int segmentSize = (int) ((outputLimit - outputAddress + 3) / 4);
+
+ long outputStart2 = outputAddress + segmentSize;
+ long outputStart3 = outputStart2 + segmentSize;
+ long outputStart4 = outputStart3 + segmentSize;
+
+ long output1 = outputAddress;
+ long output2 = outputStart2;
+ long output3 = outputStart3;
+ long output4 = outputStart4;
+
+ long fastOutputLimit = outputLimit - 7;
+ int tableLog = this.tableLog;
+ byte[] numbersOfBits = this.numbersOfBits;
+ byte[] symbols = this.symbols;
+
+ while (output4 < fastOutputLimit) {
+ stream1bitsConsumed = decodeSymbol(outputBase, output1, stream1bits, stream1bitsConsumed, tableLog, numbersOfBits, symbols);
+ stream2bitsConsumed = decodeSymbol(outputBase, output2, stream2bits, stream2bitsConsumed, tableLog, numbersOfBits, symbols);
+ stream3bitsConsumed = decodeSymbol(outputBase, output3, stream3bits, stream3bitsConsumed, tableLog, numbersOfBits, symbols);
+ stream4bitsConsumed = decodeSymbol(outputBase, output4, stream4bits, stream4bitsConsumed, tableLog, numbersOfBits, symbols);
+
+ stream1bitsConsumed = decodeSymbol(outputBase, output1 + 1, stream1bits, stream1bitsConsumed, tableLog, numbersOfBits, symbols);
+ stream2bitsConsumed = decodeSymbol(outputBase, output2 + 1, stream2bits, stream2bitsConsumed, tableLog, numbersOfBits, symbols);
+ stream3bitsConsumed = decodeSymbol(outputBase, output3 + 1, stream3bits, stream3bitsConsumed, tableLog, numbersOfBits, symbols);
+ stream4bitsConsumed = decodeSymbol(outputBase, output4 + 1, stream4bits, stream4bitsConsumed, tableLog, numbersOfBits, symbols);
+
+ stream1bitsConsumed = decodeSymbol(outputBase, output1 + 2, stream1bits, stream1bitsConsumed, tableLog, numbersOfBits, symbols);
+ stream2bitsConsumed = decodeSymbol(outputBase, output2 + 2, stream2bits, stream2bitsConsumed, tableLog, numbersOfBits, symbols);
+ stream3bitsConsumed = decodeSymbol(outputBase, output3 + 2, stream3bits, stream3bitsConsumed, tableLog, numbersOfBits, symbols);
+ stream4bitsConsumed = decodeSymbol(outputBase, output4 + 2, stream4bits, stream4bitsConsumed, tableLog, numbersOfBits, symbols);
+
+ stream1bitsConsumed = decodeSymbol(outputBase, output1 + 3, stream1bits, stream1bitsConsumed, tableLog, numbersOfBits, symbols);
+ stream2bitsConsumed = decodeSymbol(outputBase, output2 + 3, stream2bits, stream2bitsConsumed, tableLog, numbersOfBits, symbols);
+ stream3bitsConsumed = decodeSymbol(outputBase, output3 + 3, stream3bits, stream3bitsConsumed, tableLog, numbersOfBits, symbols);
+ stream4bitsConsumed = decodeSymbol(outputBase, output4 + 3, stream4bits, stream4bitsConsumed, tableLog, numbersOfBits, symbols);
+
+ output1 += SIZE_OF_INT;
+ output2 += SIZE_OF_INT;
+ output3 += SIZE_OF_INT;
+ output4 += SIZE_OF_INT;
+
+ BitStream.Loader loader = new BitStream.Loader(inputBase, start1, stream1currentAddress, stream1bits, stream1bitsConsumed);
+ boolean done = loader.load();
+ stream1bitsConsumed = loader.getBitsConsumed();
+ stream1bits = loader.getBits();
+ stream1currentAddress = loader.getCurrentAddress();
+
+ if (done) {
+ break;
+ }
+
+ loader = new BitStream.Loader(inputBase, start2, stream2currentAddress, stream2bits, stream2bitsConsumed);
+ done = loader.load();
+ stream2bitsConsumed = loader.getBitsConsumed();
+ stream2bits = loader.getBits();
+ stream2currentAddress = loader.getCurrentAddress();
+
+ if (done) {
+ break;
+ }
+
+ loader = new BitStream.Loader(inputBase, start3, stream3currentAddress, stream3bits, stream3bitsConsumed);
+ done = loader.load();
+ stream3bitsConsumed = loader.getBitsConsumed();
+ stream3bits = loader.getBits();
+ stream3currentAddress = loader.getCurrentAddress();
+ if (done) {
+ break;
+ }
+
+ loader = new BitStream.Loader(inputBase, start4, stream4currentAddress, stream4bits, stream4bitsConsumed);
+ done = loader.load();
+ stream4bitsConsumed = loader.getBitsConsumed();
+ stream4bits = loader.getBits();
+ stream4currentAddress = loader.getCurrentAddress();
+ if (done) {
+ break;
+ }
+ }
+
+ verify(output1 <= outputStart2 && output2 <= outputStart3 && output3 <= outputStart4, inputAddress, "Input is corrupted");
+
+ /// finish streams one by one
+ decodeTail(inputBase, start1, stream1currentAddress, stream1bitsConsumed, stream1bits, outputBase, output1, outputStart2);
+ decodeTail(inputBase, start2, stream2currentAddress, stream2bitsConsumed, stream2bits, outputBase, output2, outputStart3);
+ decodeTail(inputBase, start3, stream3currentAddress, stream3bitsConsumed, stream3bits, outputBase, output3, outputStart4);
+ decodeTail(inputBase, start4, stream4currentAddress, stream4bitsConsumed, stream4bits, outputBase, output4, outputLimit);
+ }
+
+ private void decodeTail(final Object inputBase, final long startAddress, long currentAddress, int bitsConsumed, long bits, final Object outputBase, long outputAddress, final long outputLimit)
+ {
+ int tableLog = this.tableLog;
+ byte[] numbersOfBits = this.numbersOfBits;
+ byte[] symbols = this.symbols;
+
+ // closer to the end
+ while (outputAddress < outputLimit) {
+ BitStream.Loader loader = new BitStream.Loader(inputBase, startAddress, currentAddress, bits, bitsConsumed);
+ boolean done = loader.load();
+ bitsConsumed = loader.getBitsConsumed();
+ bits = loader.getBits();
+ currentAddress = loader.getCurrentAddress();
+ if (done) {
+ break;
+ }
+
+ bitsConsumed = decodeSymbol(outputBase, outputAddress++, bits, bitsConsumed, tableLog, numbersOfBits, symbols);
+ }
+
+ // not more data in bit stream, so no need to reload
+ while (outputAddress < outputLimit) {
+ bitsConsumed = decodeSymbol(outputBase, outputAddress++, bits, bitsConsumed, tableLog, numbersOfBits, symbols);
+ }
+
+ verify(isEndOfStream(startAddress, currentAddress, bitsConsumed), startAddress, "Bit stream is not fully consumed");
+ }
+
+ private static int decodeSymbol(Object outputBase, long outputAddress, long bitContainer, int bitsConsumed, int tableLog, byte[] numbersOfBits, byte[] symbols)
+ {
+ int value = (int) peekBitsFast(bitsConsumed, bitContainer, tableLog);
+ UNSAFE.putByte(outputBase, outputAddress, symbols[value]);
+ return bitsConsumed + numbersOfBits[value];
+ }
+}
diff --git a/core/src/main/java/com/boydti/fawe/object/io/zstd/MalformedInputException.java b/core/src/main/java/com/boydti/fawe/object/io/zstd/MalformedInputException.java
new file mode 100644
index 00000000..f0ef81b6
--- /dev/null
+++ b/core/src/main/java/com/boydti/fawe/object/io/zstd/MalformedInputException.java
@@ -0,0 +1,23 @@
+package com.boydti.fawe.object.io.zstd;
+
+public class MalformedInputException
+ extends RuntimeException
+{
+ private final long offset;
+
+ public MalformedInputException(long offset)
+ {
+ this(offset, "Malformed input");
+ }
+
+ public MalformedInputException(long offset, String reason)
+ {
+ super(reason + ": offset=" + offset);
+ this.offset = offset;
+ }
+
+ public long getOffset()
+ {
+ return offset;
+ }
+}
diff --git a/core/src/main/java/com/boydti/fawe/object/io/zstd/UnsafeUtil.java b/core/src/main/java/com/boydti/fawe/object/io/zstd/UnsafeUtil.java
new file mode 100644
index 00000000..6b6bbe16
--- /dev/null
+++ b/core/src/main/java/com/boydti/fawe/object/io/zstd/UnsafeUtil.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.boydti.fawe.object.io.zstd;
+
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import sun.misc.Unsafe;
+
+import java.lang.reflect.Field;
+import java.nio.Buffer;
+
+final class UnsafeUtil
+{
+ public static final Unsafe UNSAFE;
+ private static final Field ADDRESS_ACCESSOR;
+
+ private UnsafeUtil() {}
+
+ static {
+ try {
+ Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
+ theUnsafe.setAccessible(true);
+ UNSAFE = (Unsafe) theUnsafe.get(null);
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ try {
+ Field field = Buffer.class.getDeclaredField("address");
+ field.setAccessible(true);
+ ADDRESS_ACCESSOR = field;
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static long getAddress(Buffer buffer)
+ {
+ try {
+ return (long) ADDRESS_ACCESSOR.get(buffer);
+ }
+ catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/core/src/main/java/com/boydti/fawe/object/io/zstd/Util.java b/core/src/main/java/com/boydti/fawe/object/io/zstd/Util.java
new file mode 100644
index 00000000..48475665
--- /dev/null
+++ b/core/src/main/java/com/boydti/fawe/object/io/zstd/Util.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.boydti.fawe.object.io.zstd;
+
+class Util
+{
+ private Util()
+ {
+ }
+
+ public static int highestBit(int value)
+ {
+ return 31 - Integer.numberOfLeadingZeros(value);
+ }
+
+ public static boolean isPowerOf2(int value)
+ {
+ return (value & (value - 1)) == 0;
+ }
+
+ public static int mask(int bits)
+ {
+ return (1 << bits) - 1;
+ }
+
+ public static void verify(boolean condition, long offset, String reason)
+ {
+ if (!condition) {
+ throw new MalformedInputException(offset, reason);
+ }
+ }
+
+ public static MalformedInputException fail(long offset, String reason)
+ {
+ throw new MalformedInputException(offset, reason);
+ }
+}
diff --git a/core/src/main/java/com/boydti/fawe/object/io/zstd/ZstdDecompressor.java b/core/src/main/java/com/boydti/fawe/object/io/zstd/ZstdDecompressor.java
new file mode 100644
index 00000000..fdba71ba
--- /dev/null
+++ b/core/src/main/java/com/boydti/fawe/object/io/zstd/ZstdDecompressor.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.boydti.fawe.object.io.zstd;
+
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+import java.nio.ByteBuffer;
+
+
+import static com.boydti.fawe.object.io.zstd.UnsafeUtil.getAddress;
+import static sun.misc.Unsafe.ARRAY_BYTE_BASE_OFFSET;
+
+public class ZstdDecompressor
+{
+ private final ZstdFrameDecompressor decompressor = new ZstdFrameDecompressor();
+
+ public int decompress(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset, int maxOutputLength)
+ throws MalformedInputException
+ {
+ long inputAddress = ARRAY_BYTE_BASE_OFFSET + inputOffset;
+ long inputLimit = inputAddress + inputLength;
+ long outputAddress = ARRAY_BYTE_BASE_OFFSET + outputOffset;
+ long outputLimit = outputAddress + maxOutputLength;
+
+ return decompressor.decompress(input, inputAddress, inputLimit, output, outputAddress, outputLimit);
+ }
+
+ public void decompress(ByteBuffer input, ByteBuffer output)
+ throws MalformedInputException
+ {
+ Object inputBase;
+ long inputAddress;
+ long inputLimit;
+ if (input.isDirect()) {
+ inputBase = null;
+ long address = getAddress(input);
+ inputAddress = address + input.position();
+ inputLimit = address + input.limit();
+ }
+ else if (input.hasArray()) {
+ inputBase = input.array();
+ inputAddress = ARRAY_BYTE_BASE_OFFSET + input.arrayOffset() + input.position();
+ inputLimit = ARRAY_BYTE_BASE_OFFSET + input.arrayOffset() + input.limit();
+ }
+ else {
+ throw new IllegalArgumentException("Unsupported input ByteBuffer implementation " + input.getClass().getName());
+ }
+
+ Object outputBase;
+ long outputAddress;
+ long outputLimit;
+ if (output.isDirect()) {
+ outputBase = null;
+ long address = getAddress(output);
+ outputAddress = address + output.position();
+ outputLimit = address + output.limit();
+ }
+ else if (output.hasArray()) {
+ outputBase = output.array();
+ outputAddress = ARRAY_BYTE_BASE_OFFSET + output.arrayOffset() + output.position();
+ outputLimit = ARRAY_BYTE_BASE_OFFSET + output.arrayOffset() + output.limit();
+ }
+ else {
+ throw new IllegalArgumentException("Unsupported output ByteBuffer implementation " + output.getClass().getName());
+ }
+
+ // HACK: Assure JVM does not collect Slice wrappers while decompressing, since the
+ // collection may trigger freeing of the underlying memory resulting in a segfault
+ // There is no other known way to signal to the JVM that an object should not be
+ // collected in a block, and technically, the JVM is allowed to eliminate these locks.
+ synchronized (input) {
+ synchronized (output) {
+ int written = new ZstdFrameDecompressor().decompress(inputBase, inputAddress, inputLimit, outputBase, outputAddress, outputLimit);
+ output.position(output.position() + written);
+ }
+ }
+ }
+
+ public static long getDecompressedSize(byte[] input, int offset, int length)
+ {
+ int baseAddress = ARRAY_BYTE_BASE_OFFSET + offset;
+ return ZstdFrameDecompressor.getDecompressedSize(input, baseAddress, baseAddress + length);
+ }
+}
diff --git a/core/src/main/java/com/boydti/fawe/object/io/zstd/ZstdFrameDecompressor.java b/core/src/main/java/com/boydti/fawe/object/io/zstd/ZstdFrameDecompressor.java
new file mode 100644
index 00000000..83c359fb
--- /dev/null
+++ b/core/src/main/java/com/boydti/fawe/object/io/zstd/ZstdFrameDecompressor.java
@@ -0,0 +1,958 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.boydti.fawe.object.io.zstd;
+
+import java.util.Arrays;
+
+
+import static com.boydti.fawe.object.io.zstd.BitStream.peekBits;
+import static com.boydti.fawe.object.io.zstd.UnsafeUtil.UNSAFE;
+import static com.boydti.fawe.object.io.zstd.Util.fail;
+import static com.boydti.fawe.object.io.zstd.Util.mask;
+import static com.boydti.fawe.object.io.zstd.Util.verify;
+import static sun.misc.Unsafe.ARRAY_BYTE_BASE_OFFSET;
+
+class ZstdFrameDecompressor
+{
+ private static final int MIN_MATCH = 3;
+
+ private static final int[] DEC_32_TABLE = {4, 1, 2, 1, 4, 4, 4, 4};
+ private static final int[] DEC_64_TABLE = {0, 0, 0, -1, 0, 1, 2, 3};
+
+ private static final int MAGIC_NUMBER = 0xFD2FB528; // v0.5
+
+ private static final int MIN_SEQUENCES_SIZE = 1;
+ private static final int MIN_BLOCK_SIZE = 1 // block type tag
+ + 1 // min size of raw or rle length header
+ + MIN_SEQUENCES_SIZE;
+
+ private static final int MAX_BLOCK_SIZE = 128 * 1024;
+
+ private static final int MIN_WINDOW_LOG = 10;
+ private static final int MAX_WINDOW_SIZE = 1 << 23;
+
+ public static final int SIZE_OF_BYTE = 1;
+ public static final int SIZE_OF_SHORT = 2;
+ public static final int SIZE_OF_INT = 4;
+ public static final int SIZE_OF_LONG = 8;
+
+ private static final long SIZE_OF_BLOCK_HEADER = 3;
+
+ // block types
+ private static final int RAW_BLOCK = 0;
+ private static final int RLE_BLOCK = 1;
+ private static final int COMPRESSED_BLOCK = 2;
+
+ // literal block types
+ private static final int RAW_LITERALS_BLOCK = 0;
+ private static final int RLE_LITERALS_BLOCK = 1;
+ private static final int COMPRESSED_LITERALS_BLOCK = 2;
+ private static final int REPEAT_STATS_LITERALS_BLOCK = 3;
+
+ private static final int LONG_NUMBER_OF_SEQUENCES = 0x7F00;
+
+ private static final int MAX_LITERALS_LENGTH_SYMBOL = 35;
+ private static final int MAX_MATCH_LENGTH_SYMBOL = 52;
+ private static final int MAX_OFFSET_CODE_SYMBOL = 28;
+
+ private static final int LITERALS_LENGTH_FSE_LOG = 9;
+ private static final int MATCH_LENGTH_FSE_LOG = 9;
+ private static final int OFFSET_CODES_FSE_LOG = 8;
+
+ private static final int SET_BASIC = 0;
+ private static final int SET_RLE = 1;
+ private static final int SET_COMPRESSED = 2;
+ private static final int SET_REPEAT = 3;
+
+ private static final int[] LITERALS_LENGTH_BASE = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 18, 20, 22, 24, 28, 32, 40, 48, 64, 0x80, 0x100, 0x200, 0x400, 0x800, 0x1000,
+ 0x2000, 0x4000, 0x8000, 0x10000};
+
+ private static final int[] MATCH_LENGTH_BASE = {
+ 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
+ 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
+ 35, 37, 39, 41, 43, 47, 51, 59, 67, 83, 99, 0x83, 0x103, 0x203, 0x403, 0x803,
+ 0x1003, 0x2003, 0x4003, 0x8003, 0x10003};
+
+ private static final int[] OFFSET_CODES_BASE = {
+ 0, 1, 1, 5, 0xD, 0x1D, 0x3D, 0x7D,
+ 0xFD, 0x1FD, 0x3FD, 0x7FD, 0xFFD, 0x1FFD, 0x3FFD, 0x7FFD,
+ 0xFFFD, 0x1FFFD, 0x3FFFD, 0x7FFFD, 0xFFFFD, 0x1FFFFD, 0x3FFFFD, 0x7FFFFD,
+ 0xFFFFFD, 0x1FFFFFD, 0x3FFFFFD, 0x7FFFFFD, 0xFFFFFFD};
+
+ private static final int[] LITERALS_LENGTH_BITS = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1, 1, 1, 2, 2, 3, 3, 4, 6, 7, 8, 9, 10, 11, 12,
+ 13, 14, 15, 16};
+
+ private static final int[] MATCH_LENGTH_BITS = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 7, 8, 9, 10, 11,
+ 12, 13, 14, 15, 16};
+
+ private static final FiniteStateEntropy.Table DEFAULT_LITERALS_LENGTH_TABLE = new FiniteStateEntropy.Table(
+ 6,
+ new int[] {
+ 0, 16, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 32, 0, 0, 32, 0, 32, 0, 32, 0, 0, 32, 0, 32, 0, 32, 0, 0, 16, 32, 0, 0, 48, 16, 32, 32, 32,
+ 32, 32, 32, 32, 32, 0, 32, 32, 32, 32, 32, 32, 0, 0, 0, 0},
+ new byte[] {
+ 0, 0, 1, 3, 4, 6, 7, 9, 10, 12, 14, 16, 18, 19, 21, 22, 24, 25, 26, 27, 29, 31, 0, 1, 2, 4, 5, 7, 8, 10, 11, 13, 16, 17, 19, 20, 22, 23, 25, 25, 26, 28, 30, 0,
+ 1, 2, 3, 5, 6, 8, 9, 11, 12, 15, 17, 18, 20, 21, 23, 24, 35, 34, 33, 32},
+ new byte[] {
+ 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 4, 4, 5, 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 5, 5, 4, 4, 5, 6, 6, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5,
+ 6, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6});
+
+ private static final FiniteStateEntropy.Table DEFAULT_OFFSET_CODES_TABLE = new FiniteStateEntropy.Table(
+ 5,
+ new int[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0},
+ new byte[] {0, 6, 9, 15, 21, 3, 7, 12, 18, 23, 5, 8, 14, 20, 2, 7, 11, 17, 22, 4, 8, 13, 19, 1, 6, 10, 16, 28, 27, 26, 25, 24},
+ new byte[] {5, 4, 5, 5, 5, 5, 4, 5, 5, 5, 5, 4, 5, 5, 5, 4, 5, 5, 5, 5, 4, 5, 5, 5, 4, 5, 5, 5, 5, 5, 5, 5});
+
+ private static final FiniteStateEntropy.Table DEFAULT_MATCH_LENGTH_TABLE = new FiniteStateEntropy.Table(
+ 6,
+ new int[] {
+ 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 32, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 48, 16, 32, 32, 32, 32,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
+ new byte[] {
+ 0, 1, 2, 3, 5, 6, 8, 10, 13, 16, 19, 22, 25, 28, 31, 33, 35, 37, 39, 41, 43, 45, 1, 2, 3, 4, 6, 7, 9, 12, 15, 18, 21, 24, 27, 30, 32, 34, 36, 38, 40, 42, 44, 1,
+ 1, 2, 4, 5, 7, 8, 11, 14, 17, 20, 23, 26, 29, 52, 51, 50, 49, 48, 47, 46},
+ new byte[] {
+ 6, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6});
+
+ private final byte[] literals = new byte[MAX_BLOCK_SIZE + SIZE_OF_LONG]; // extra space to allow for long-at-a-time copy
+
+ // current buffer containing literals
+ private Object literalsBase;
+ private long literalsAddress;
+ private long literalsLimit;
+
+ private final int[] previousOffsets = new int[3];
+
+ private final FiniteStateEntropy.Table literalsLengthTable = new FiniteStateEntropy.Table(LITERALS_LENGTH_FSE_LOG);
+ private final FiniteStateEntropy.Table offsetCodesTable = new FiniteStateEntropy.Table(OFFSET_CODES_FSE_LOG);
+ private final FiniteStateEntropy.Table matchLengthTable = new FiniteStateEntropy.Table(MATCH_LENGTH_FSE_LOG);
+
+ private FiniteStateEntropy.Table currentLiteralsLengthTable;
+ private FiniteStateEntropy.Table currentOffsetCodesTable;
+ private FiniteStateEntropy.Table currentMatchLengthTable;
+
+ private final Huffman huffman = new Huffman();
+ private final FseTableReader fse = new FseTableReader();
+
+ public int decompress(
+ final Object inputBase,
+ final long inputAddress,
+ final long inputLimit,
+ final Object outputBase,
+ final long outputAddress,
+ final long outputLimit)
+ {
+ if (outputAddress == outputLimit) {
+ return 0;
+ }
+
+ reset();
+
+ long input = inputAddress;
+ long output = outputAddress;
+
+ input += verifyMagic(inputBase, inputAddress, inputLimit);
+
+ FrameHeader frameHeader = readFrameHeader(inputBase, input, inputLimit);
+ input += frameHeader.headerSize;
+
+ boolean lastBlock;
+ do {
+ verify(input + SIZE_OF_BLOCK_HEADER <= inputLimit, input, "Not enough input bytes");
+
+ // read block header
+ int header = UNSAFE.getInt(inputBase, input) & 0xFF_FFFF;
+ input += SIZE_OF_BLOCK_HEADER;
+
+ lastBlock = (header & 1) != 0;
+ int blockType = (header >>> 1) & 0b11;
+ int blockSize = (header >>> 3) & 0x1F_FFFF; // 21 bits
+
+ int decodedSize;
+ switch (blockType) {
+ case RAW_BLOCK:
+ verify(inputAddress + blockSize <= inputLimit, input, "Not enough input bytes");
+ decodedSize = decodeRawBlock(inputBase, input, blockSize, outputBase, output, outputLimit);
+ input += blockSize;
+ break;
+ case RLE_BLOCK:
+ verify(inputAddress + 1 <= inputLimit, input, "Not enough input bytes");
+ decodedSize = decodeRleBlock(blockSize, inputBase, input, outputBase, output, outputLimit);
+ input += 1;
+ break;
+ case COMPRESSED_BLOCK:
+ verify(inputAddress + blockSize <= inputLimit, input, "Not enough input bytes");
+ decodedSize = decodeCompressedBlock(inputBase, input, blockSize, outputBase, output, outputLimit, frameHeader.windowSize);
+ input += blockSize;
+ break;
+ default:
+ throw fail(input, "Invalid block type");
+ }
+
+ output += decodedSize;
+ }
+ while (!lastBlock);
+
+ if (frameHeader.hasChecksum) {
+ // TODO checksum
+ }
+
+ return (int) (output - outputAddress);
+ }
+
+ private void reset()
+ {
+ previousOffsets[0] = 1;
+ previousOffsets[1] = 4;
+ previousOffsets[2] = 8;
+
+ currentLiteralsLengthTable = null;
+ currentOffsetCodesTable = null;
+ currentMatchLengthTable = null;
+ }
+
+ private static int decodeRawBlock(Object inputBase, long inputAddress, int blockSize, Object outputBase, long outputAddress, long outputLimit)
+ {
+ verify(outputAddress + blockSize <= outputLimit, inputAddress, "Output buffer too small");
+
+ UNSAFE.copyMemory(inputBase, inputAddress, outputBase, outputAddress, blockSize);
+ return blockSize;
+ }
+
+ private static int decodeRleBlock(int size, Object inputBase, long inputAddress, Object outputBase, long outputAddress, long outputLimit)
+ {
+ verify(outputAddress + size <= outputLimit, inputAddress, "Output buffer too small");
+
+ long output = outputAddress;
+ long value = UNSAFE.getByte(inputBase, inputAddress) & 0xFFL;
+
+ int remaining = size;
+ if (remaining >= SIZE_OF_LONG) {
+ long packed = value
+ | (value << 8)
+ | (value << 16)
+ | (value << 24)
+ | (value << 32)
+ | (value << 40)
+ | (value << 48)
+ | (value << 56);
+
+ do {
+ UNSAFE.putLong(outputBase, output, packed);
+ output += SIZE_OF_LONG;
+ remaining -= SIZE_OF_LONG;
+ }
+ while (remaining >= SIZE_OF_LONG);
+ }
+
+ for (int i = 0; i < remaining; i++) {
+ UNSAFE.putByte(outputBase, output, (byte) value);
+ output++;
+ }
+
+ return size;
+ }
+
+ private int decodeCompressedBlock(Object inputBase, final long inputAddress, int blockSize, Object outputBase, long outputAddress, long outputLimit, int windowSize)
+ {
+ long inputLimit = inputAddress + blockSize;
+ long input = inputAddress;
+
+ verify(blockSize <= MAX_BLOCK_SIZE, input, "Expected match length table to be present");
+ verify(blockSize >= MIN_BLOCK_SIZE, input, "Compressed block size too small");
+
+ // decode literals
+ int literalsBlockType = UNSAFE.getByte(inputBase, input) & 0b11;
+
+ switch (literalsBlockType) {
+ case RAW_LITERALS_BLOCK: {
+ input += decodeRawLiterals(inputBase, input, inputLimit);
+ break;
+ }
+ case RLE_LITERALS_BLOCK: {
+ input += decodeRleLiterals(inputBase, input, blockSize);
+ break;
+ }
+ case REPEAT_STATS_LITERALS_BLOCK:
+ verify(huffman.isLoaded(), input, "Dictionary is corrupted");
+ case COMPRESSED_LITERALS_BLOCK: {
+ input += decodeCompressedLiterals(inputBase, input, blockSize, literalsBlockType);
+ break;
+ }
+ default:
+ throw fail(input, "Invalid literals block encoding type");
+ }
+
+ verify(windowSize <= MAX_WINDOW_SIZE, input, "Window size too large (not yet supported)");
+
+ return decompressSequences(
+ inputBase, input, inputAddress + blockSize,
+ outputBase, outputAddress, outputLimit,
+ literalsBase, literalsAddress, literalsLimit);
+ }
+
+ private int decompressSequences(
+ final Object inputBase, final long inputAddress, final long inputLimit,
+ final Object outputBase, final long outputAddress, final long outputLimit,
+ final Object literalsBase, final long literalsAddress, final long literalsLimit)
+ {
+ final long fastOutputLimit = outputLimit - SIZE_OF_LONG;
+
+ long input = inputAddress;
+ long output = outputAddress;
+
+ long literalsInput = literalsAddress;
+
+ int size = (int) (inputLimit - inputAddress);
+ verify(size >= MIN_SEQUENCES_SIZE, input, "Not enough input bytes");
+
+ // decode header
+ int sequenceCount = UNSAFE.getByte(inputBase, input++) & 0xFF;
+ if (sequenceCount != 0) {
+ if (sequenceCount == 255) {
+ verify(input + SIZE_OF_SHORT <= inputLimit, input, "Not enough input bytes");
+ sequenceCount = (UNSAFE.getShort(inputBase, input) & 0xFFFF) + LONG_NUMBER_OF_SEQUENCES;
+ input += SIZE_OF_SHORT;
+ }
+ else if (sequenceCount > 127) {
+ verify(input < inputLimit, input, "Not enough input bytes");
+ sequenceCount = ((sequenceCount - 128) << 8) + (UNSAFE.getByte(inputBase, input++) & 0xFF);
+ }
+
+ verify(input + SIZE_OF_INT <= inputLimit, input, "Not enough input bytes");
+
+ byte type = UNSAFE.getByte(inputBase, input++);
+
+ int literalsLengthType = (type & 0xFF) >>> 6;
+ int offsetCodesType = (type >>> 4) & 0b11;
+ int matchLengthType = (type >>> 2) & 0b11;
+
+ input = computeLiteralsTable(literalsLengthType, inputBase, input, inputLimit);
+ input = computeOffsetsTable(offsetCodesType, inputBase, input, inputLimit);
+ input = computeMatchLengthTable(matchLengthType, inputBase, input, inputLimit);
+
+ // decompress sequences
+ BitStream.Initializer initializer = new BitStream.Initializer(inputBase, input, inputLimit);
+ initializer.initialize();
+ int bitsConsumed = initializer.getBitsConsumed();
+ long bits = initializer.getBits();
+ long currentAddress = initializer.getCurrentAddress();
+
+ FiniteStateEntropy.Table currentLiteralsLengthTable = this.currentLiteralsLengthTable;
+ FiniteStateEntropy.Table currentOffsetCodesTable = this.currentOffsetCodesTable;
+ FiniteStateEntropy.Table currentMatchLengthTable = this.currentMatchLengthTable;
+
+ int literalsLengthState = (int) peekBits(bitsConsumed, bits, currentLiteralsLengthTable.log2Size);
+ bitsConsumed += currentLiteralsLengthTable.log2Size;
+
+ int offsetCodesState = (int) peekBits(bitsConsumed, bits, currentOffsetCodesTable.log2Size);
+ bitsConsumed += currentOffsetCodesTable.log2Size;
+
+ int matchLengthState = (int) peekBits(bitsConsumed, bits, currentMatchLengthTable.log2Size);
+ bitsConsumed += currentMatchLengthTable.log2Size;
+
+ int[] previousOffsets = this.previousOffsets;
+
+ byte[] literalsLengthNumbersOfBits = currentLiteralsLengthTable.numberOfBits;
+ int[] literalsLengthNewStates = currentLiteralsLengthTable.newState;
+ byte[] literalsLengthSymbols = currentLiteralsLengthTable.symbol;
+
+ byte[] matchLengthNumbersOfBits = currentMatchLengthTable.numberOfBits;
+ int[] matchLengthNewStates = currentMatchLengthTable.newState;
+ byte[] matchLengthSymbols = currentMatchLengthTable.symbol;
+
+ byte[] offsetCodesNumbersOfBits = currentOffsetCodesTable.numberOfBits;
+ int[] offsetCodesNewStates = currentOffsetCodesTable.newState;
+ byte[] offsetCodesSymbols = currentOffsetCodesTable.symbol;
+
+ while (sequenceCount > 0) {
+ sequenceCount--;
+
+ BitStream.Loader loader = new BitStream.Loader(inputBase, input, currentAddress, bits, bitsConsumed);
+ loader.load();
+ bitsConsumed = loader.getBitsConsumed();
+ bits = loader.getBits();
+ currentAddress = loader.getCurrentAddress();
+ if (loader.isOverflow()) {
+ verify(sequenceCount == 0, input, "Not all sequences were consumed");
+ break;
+ }
+
+ // decode sequence
+ int literalsLengthCode = literalsLengthSymbols[literalsLengthState];
+ int matchLengthCode = matchLengthSymbols[matchLengthState];
+ int offsetCode = offsetCodesSymbols[offsetCodesState];
+
+ int literalsLengthBits = LITERALS_LENGTH_BITS[literalsLengthCode];
+ int matchLengthBits = MATCH_LENGTH_BITS[matchLengthCode];
+ int offsetBits = offsetCode;
+
+ int offset = OFFSET_CODES_BASE[offsetCode];
+ if (offsetCode > 0) {
+ offset += peekBits(bitsConsumed, bits, offsetBits);
+ bitsConsumed += offsetBits;
+ }
+
+ if (offsetCode <= 1) {
+ if (literalsLengthCode == 0) {
+ offset++;
+ }
+
+ if (offset != 0) {
+ int temp;
+ if (offset == 3) {
+ temp = previousOffsets[0] - 1;
+ }
+ else {
+ temp = previousOffsets[offset];
+ }
+
+ if (temp == 0) {
+ temp = 1;
+ }
+
+ if (offset != 1) {
+ previousOffsets[2] = previousOffsets[1];
+ }
+ previousOffsets[1] = previousOffsets[0];
+ previousOffsets[0] = temp;
+
+ offset = temp;
+ }
+ else {
+ offset = previousOffsets[0];
+ }
+ }
+ else {
+ previousOffsets[2] = previousOffsets[1];
+ previousOffsets[1] = previousOffsets[0];
+ previousOffsets[0] = offset;
+ }
+
+ int matchLength = MATCH_LENGTH_BASE[matchLengthCode];
+ if (matchLengthCode > 31) {
+ matchLength += peekBits(bitsConsumed, bits, matchLengthBits);
+ bitsConsumed += matchLengthBits;
+ }
+
+ int literalsLength = LITERALS_LENGTH_BASE[literalsLengthCode];
+ if (literalsLengthCode > 15) {
+ literalsLength += peekBits(bitsConsumed, bits, literalsLengthBits);
+ bitsConsumed += literalsLengthBits;
+ }
+
+ int totalBits = literalsLengthBits + matchLengthBits + offsetBits;
+ if (totalBits > 64 - 7 - (LITERALS_LENGTH_FSE_LOG + MATCH_LENGTH_FSE_LOG + OFFSET_CODES_FSE_LOG)) {
+ BitStream.Loader loader1 = new BitStream.Loader(inputBase, input, currentAddress, bits, bitsConsumed);
+ loader1.load();
+
+ bitsConsumed = loader1.getBitsConsumed();
+ bits = loader1.getBits();
+ currentAddress = loader1.getCurrentAddress();
+ }
+
+ int numberOfBits;
+
+ numberOfBits = literalsLengthNumbersOfBits[literalsLengthState];
+ literalsLengthState = (int) (literalsLengthNewStates[literalsLengthState] + peekBits(bitsConsumed, bits, numberOfBits)); // <= 9 bits
+ bitsConsumed += numberOfBits;
+
+ numberOfBits = matchLengthNumbersOfBits[matchLengthState];
+ matchLengthState = (int) (matchLengthNewStates[matchLengthState] + peekBits(bitsConsumed, bits, numberOfBits)); // <= 9 bits
+ bitsConsumed += numberOfBits;
+
+ numberOfBits = offsetCodesNumbersOfBits[offsetCodesState];
+ offsetCodesState = (int) (offsetCodesNewStates[offsetCodesState] + peekBits(bitsConsumed, bits, numberOfBits)); // <= 8 bits
+ bitsConsumed += numberOfBits;
+
+ final long literalOutputLimit = output + literalsLength;
+ final long matchOutputLimit = literalOutputLimit + matchLength;
+
+ verify(matchOutputLimit <= outputLimit, input, "Output buffer too small");
+ verify(literalsInput + literalsLength <= literalsLimit, input, "Input is corrupted");
+
+ long matchAddress = literalOutputLimit - offset;
+
+ if (literalOutputLimit > fastOutputLimit) {
+ executeLastSequence(outputBase, output, literalOutputLimit, matchOutputLimit, fastOutputLimit, literalsInput, matchAddress);
+ }
+ else {
+ // copy literals. literalOutputLimit <= fastOutputLimit, so we can copy
+ // long at a time with over-copy
+ output = copyLiterals(outputBase, literalsBase, output, literalsInput, literalOutputLimit);
+ copyMatch(outputBase, fastOutputLimit, output, offset, matchOutputLimit, matchAddress);
+ }
+ output = matchOutputLimit;
+ literalsInput += literalsLength;
+ }
+ }
+
+ // last literal segment
+ output = copyLastLiteral(outputBase, literalsBase, literalsLimit, output, literalsInput);
+
+ return (int) (output - outputAddress);
+ }
+
+ private long copyLastLiteral(Object outputBase, Object literalsBase, long literalsLimit, long output, long literalsInput)
+ {
+ long lastLiteralsSize = literalsLimit - literalsInput;
+ UNSAFE.copyMemory(literalsBase, literalsInput, outputBase, output, lastLiteralsSize);
+ output += lastLiteralsSize;
+ return output;
+ }
+
+ private void copyMatch(Object outputBase, long fastOutputLimit, long output, int offset, long matchOutputLimit, long matchAddress)
+ {
+ matchAddress = copyMatchHead(outputBase, output, offset, matchAddress);
+ output += SIZE_OF_LONG;
+
+ copyMatchTail(outputBase, fastOutputLimit, output, matchOutputLimit, matchAddress);
+ }
+
+ private void copyMatchTail(Object outputBase, long fastOutputLimit, long output, long matchOutputLimit, long matchAddress)
+ {
+ if (matchOutputLimit > fastOutputLimit) {
+ while (output < fastOutputLimit) {
+ UNSAFE.putLong(outputBase, output, UNSAFE.getLong(outputBase, matchAddress));
+ matchAddress += SIZE_OF_LONG;
+ output += SIZE_OF_LONG;
+ }
+
+ while (output < matchOutputLimit) {
+ UNSAFE.putByte(outputBase, output++, UNSAFE.getByte(outputBase, matchAddress++));
+ }
+ }
+ else {
+ while (output < matchOutputLimit) {
+ UNSAFE.putLong(outputBase, output, UNSAFE.getLong(outputBase, matchAddress));
+ matchAddress += SIZE_OF_LONG;
+ output += SIZE_OF_LONG;
+ }
+ }
+ }
+
+ private long copyMatchHead(Object outputBase, long output, int offset, long matchAddress)
+ {
+ // copy match
+ if (offset < 8) {
+ // 8 bytes apart so that we can copy long-at-a-time below
+ int increment32 = DEC_32_TABLE[offset];
+ int decrement64 = DEC_64_TABLE[offset];
+
+ UNSAFE.putByte(outputBase, output, UNSAFE.getByte(outputBase, matchAddress));
+ UNSAFE.putByte(outputBase, output + 1, UNSAFE.getByte(outputBase, matchAddress + 1));
+ UNSAFE.putByte(outputBase, output + 2, UNSAFE.getByte(outputBase, matchAddress + 2));
+ UNSAFE.putByte(outputBase, output + 3, UNSAFE.getByte(outputBase, matchAddress + 3));
+ matchAddress += increment32;
+
+ UNSAFE.putInt(outputBase, output + 4, UNSAFE.getInt(outputBase, matchAddress));
+ matchAddress -= decrement64;
+ }
+ else {
+ UNSAFE.putLong(outputBase, output, UNSAFE.getLong(outputBase, matchAddress));
+ matchAddress += SIZE_OF_LONG;
+ }
+ return matchAddress;
+ }
+
+ private long copyLiterals(Object outputBase, Object literalsBase, long output, long literalsInput, long literalOutputLimit)
+ {
+ long literalInput = literalsInput;
+ do {
+ UNSAFE.putLong(outputBase, output, UNSAFE.getLong(literalsBase, literalInput));
+ output += SIZE_OF_LONG;
+ literalInput += SIZE_OF_LONG;
+ }
+ while (output < literalOutputLimit);
+ output = literalOutputLimit; // correction in case we over-copied
+ return output;
+ }
+
+ private long computeMatchLengthTable(int matchLengthType, Object inputBase, long input, long inputLimit)
+ {
+ switch (matchLengthType) {
+ case SET_RLE:
+ verify(input < inputLimit, input, "Not enough input bytes");
+
+ byte value = UNSAFE.getByte(inputBase, input++);
+ verify(value <= MAX_MATCH_LENGTH_SYMBOL, input, "Value exceeds expected maximum value");
+
+ FseTableReader.buildRleTable(matchLengthTable, value);
+ currentMatchLengthTable = matchLengthTable;
+ break;
+ case SET_BASIC:
+ currentMatchLengthTable = DEFAULT_MATCH_LENGTH_TABLE;
+ break;
+ case SET_REPEAT:
+ verify(currentMatchLengthTable != null, input, "Expected match length table to be present");
+ break;
+ case SET_COMPRESSED:
+ input += fse.readFseTable(matchLengthTable, inputBase, input, inputLimit, MAX_MATCH_LENGTH_SYMBOL, MATCH_LENGTH_FSE_LOG);
+ currentMatchLengthTable = matchLengthTable;
+ break;
+ default:
+ throw fail(input, "Invalid match length encoding type");
+ }
+ return input;
+ }
+
+ private long computeOffsetsTable(int offsetCodesType, Object inputBase, long input, long inputLimit)
+ {
+ switch (offsetCodesType) {
+ case SET_RLE:
+ verify(input < inputLimit, input, "Not enough input bytes");
+
+ byte value = UNSAFE.getByte(inputBase, input++);
+ verify(value <= MAX_OFFSET_CODE_SYMBOL, input, "Value exceeds expected maximum value");
+
+ FseTableReader.buildRleTable(offsetCodesTable, value);
+ currentOffsetCodesTable = offsetCodesTable;
+ break;
+ case SET_BASIC:
+ currentOffsetCodesTable = DEFAULT_OFFSET_CODES_TABLE;
+ break;
+ case SET_REPEAT:
+ verify(currentOffsetCodesTable != null, input, "Expected match length table to be present");
+ break;
+ case SET_COMPRESSED:
+ input += fse.readFseTable(offsetCodesTable, inputBase, input, inputLimit, MAX_OFFSET_CODE_SYMBOL, OFFSET_CODES_FSE_LOG);
+ currentOffsetCodesTable = offsetCodesTable;
+ break;
+ default:
+ throw fail(input, "Invalid offset code encoding type");
+ }
+ return input;
+ }
+
+ private long computeLiteralsTable(int literalsLengthType, Object inputBase, long input, long inputLimit)
+ {
+ switch (literalsLengthType) {
+ case SET_RLE:
+ verify(input < inputLimit, input, "Not enough input bytes");
+
+ byte value = UNSAFE.getByte(inputBase, input++);
+ verify(value <= MAX_LITERALS_LENGTH_SYMBOL, input, "Value exceeds expected maximum value");
+
+ FseTableReader.buildRleTable(literalsLengthTable, value);
+ currentLiteralsLengthTable = literalsLengthTable;
+ break;
+ case SET_BASIC:
+ currentLiteralsLengthTable = DEFAULT_LITERALS_LENGTH_TABLE;
+ break;
+ case SET_REPEAT:
+ verify(currentLiteralsLengthTable != null, input, "Expected match length table to be present");
+ break;
+ case SET_COMPRESSED:
+ input += fse.readFseTable(literalsLengthTable, inputBase, input, inputLimit, MAX_LITERALS_LENGTH_SYMBOL, LITERALS_LENGTH_FSE_LOG);
+ currentLiteralsLengthTable = literalsLengthTable;
+ break;
+ default:
+ throw fail(input, "Invalid literals length encoding type");
+ }
+ return input;
+ }
+
+ private void executeLastSequence(Object outputBase, long output, long literalOutputLimit, long matchOutputLimit, long fastOutputLimit, long literalInput, long matchAddress)
+ {
+ // copy literals
+ if (output < fastOutputLimit) {
+ // wild copy
+ do {
+ UNSAFE.putLong(outputBase, output, UNSAFE.getLong(literalsBase, literalInput));
+ output += SIZE_OF_LONG;
+ literalInput += SIZE_OF_LONG;
+ }
+ while (output < fastOutputLimit);
+
+ literalInput -= output - fastOutputLimit;
+ output = fastOutputLimit;
+ }
+
+ while (output < literalOutputLimit) {
+ UNSAFE.putByte(outputBase, output, UNSAFE.getByte(literalsBase, literalInput));
+ output++;
+ literalInput++;
+ }
+
+ // copy match
+ while (output < matchOutputLimit) {
+ UNSAFE.putByte(outputBase, output, UNSAFE.getByte(outputBase, matchAddress));
+ output++;
+ matchAddress++;
+ }
+ }
+
+ private int decodeCompressedLiterals(Object inputBase, final long inputAddress, int blockSize, int literalsBlockType)
+ {
+ long input = inputAddress;
+ verify(blockSize >= 5, input, "Not enough input bytes");
+
+ // compressed
+ int compressedSize;
+ int uncompressedSize;
+ boolean singleStream = false;
+ int headerSize;
+ int type = (UNSAFE.getByte(inputBase, input) >> 2) & 0b11;
+ switch (type) {
+ case 0:
+ singleStream = true;
+ case 1: {
+ int header = UNSAFE.getInt(inputBase, input);
+
+ headerSize = 3;
+ uncompressedSize = (header >>> 4) & mask(10);
+ compressedSize = (header >>> 14) & mask(10);
+ break;
+ }
+ case 2: {
+ int header = UNSAFE.getInt(inputBase, input);
+
+ headerSize = 4;
+ uncompressedSize = (header >>> 4) & mask(14);
+ compressedSize = (header >>> 18) & mask(14);
+ break;
+ }
+ case 3: {
+ // read 5 little-endian bytes
+ long header = UNSAFE.getByte(inputBase, input) & 0xFF |
+ (UNSAFE.getInt(inputBase, input + 1) & 0xFFFF_FFFFL) << 8;
+
+ headerSize = 5;
+ uncompressedSize = (int) ((header >>> 4) & mask(18));
+ compressedSize = (int) ((header >>> 22) & mask(18));
+ break;
+ }
+ default:
+ throw fail(input, "Invalid literals header size type");
+ }
+
+ verify(uncompressedSize <= MAX_BLOCK_SIZE, input, "Block exceeds maximum size");
+ verify(headerSize + compressedSize <= blockSize, input, "Input is corrupted");
+
+ input += headerSize;
+
+ long inputLimit = input + compressedSize;
+ if (literalsBlockType != REPEAT_STATS_LITERALS_BLOCK) {
+ input += huffman.readTable(inputBase, input, compressedSize);
+ }
+
+ literalsBase = literals;
+ literalsAddress = ARRAY_BYTE_BASE_OFFSET;
+ literalsLimit = ARRAY_BYTE_BASE_OFFSET + uncompressedSize;
+
+ if (singleStream) {
+ huffman.decodeSingleStream(inputBase, input, inputLimit, literals, literalsAddress, literalsLimit);
+ }
+ else {
+ huffman.decode4Streams(inputBase, input, inputLimit, literals, literalsAddress, literalsLimit);
+ }
+
+ return headerSize + compressedSize;
+ }
+
+ private int decodeRleLiterals(Object inputBase, final long inputAddress, int blockSize)
+ {
+ long input = inputAddress;
+ int outputSize;
+
+ int type = (UNSAFE.getByte(inputBase, input) >> 2) & 0b11;
+ switch (type) {
+ case 0:
+ case 2:
+ outputSize = (UNSAFE.getByte(inputBase, input) & 0xFF) >>> 3;
+ input++;
+ break;
+ case 1:
+ outputSize = (UNSAFE.getShort(inputBase, input) & 0xFFFF) >>> 4;
+ input += 2;
+ break;
+ case 3:
+ // we need at least 4 bytes (3 for the header, 1 for the payload)
+ verify(blockSize >= SIZE_OF_INT, input, "Not enough input bytes");
+ outputSize = (UNSAFE.getInt(inputBase, input) & 0xFF_FFFF) >>> 4;
+ input += 3;
+ break;
+ default:
+ throw fail(input, "Invalid RLE literals header encoding type");
+ }
+
+ verify(outputSize <= MAX_BLOCK_SIZE, input, "Output exceeds maximum block size");
+
+ byte value = UNSAFE.getByte(inputBase, input++);
+ Arrays.fill(literals, 0, outputSize + SIZE_OF_LONG, value);
+
+ literalsBase = literals;
+ literalsAddress = ARRAY_BYTE_BASE_OFFSET;
+ literalsLimit = ARRAY_BYTE_BASE_OFFSET + outputSize;
+
+ return (int) (input - inputAddress);
+ }
+
+ private int decodeRawLiterals(Object inputBase, final long inputAddress, long inputLimit)
+ {
+ long input = inputAddress;
+ int type = (UNSAFE.getByte(inputBase, input) >> 2) & 0b11;
+
+ int literalSize;
+ switch (type) {
+ case 0:
+ case 2:
+ literalSize = (UNSAFE.getByte(inputBase, input) & 0xFF) >>> 3;
+ input++;
+ break;
+ case 1:
+ literalSize = (UNSAFE.getShort(inputBase, input) & 0xFFFF) >>> 4;
+ input += 2;
+ break;
+ case 3:
+ // read 3 little-endian bytes
+ int header = ((UNSAFE.getByte(inputBase, input) & 0xFF) |
+ ((UNSAFE.getShort(inputBase, input + 1) & 0xFFFF) << 8));
+
+ literalSize = header >>> 4;
+ input += 3;
+ break;
+ default:
+ throw fail(input, "Invalid raw literals header encoding type");
+ }
+
+ verify(input + literalSize <= inputLimit, input, "Not enough input bytes");
+
+ // Set literals pointer to [input, literalSize], but only if we can copy 8 bytes at a time during sequence decoding
+ // Otherwise, copy literals into buffer that's big enough to guarantee that
+ if (literalSize > (inputLimit - input) - SIZE_OF_LONG) {
+ literalsBase = literals;
+ literalsAddress = ARRAY_BYTE_BASE_OFFSET;
+ literalsLimit = ARRAY_BYTE_BASE_OFFSET + literalSize;
+
+ UNSAFE.copyMemory(inputBase, input, literals, literalsAddress, literalSize);
+ Arrays.fill(literals, literalSize, literalSize + SIZE_OF_LONG, (byte) 0);
+ }
+ else {
+ literalsBase = inputBase;
+ literalsAddress = input;
+ literalsLimit = literalsAddress + literalSize;
+ }
+ input += literalSize;
+
+ return (int) (input - inputAddress);
+ }
+
+ private static FrameHeader readFrameHeader(final Object inputBase, final long inputAddress, final long inputLimit)
+ {
+ long input = inputAddress;
+ verify(input < inputLimit, input, "Not enough input bytes");
+
+ int frameHeaderDescriptor = UNSAFE.getByte(inputBase, input++) & 0xFF;
+ boolean singleSegment = (frameHeaderDescriptor & 0b100000) != 0;
+ int dictionaryDescriptor = frameHeaderDescriptor & 0b11;
+ int contentSizeDescriptor = frameHeaderDescriptor >>> 6;
+
+ int headerSize = 1 +
+ (singleSegment ? 0 : 1) +
+ (dictionaryDescriptor == 0 ? 0 : (1 << (dictionaryDescriptor - 1))) +
+ (contentSizeDescriptor == 0 ? (singleSegment ? 1 : 0) : (1 << contentSizeDescriptor));
+
+ verify(headerSize <= inputLimit - inputAddress, input, "Not enough input bytes");
+
+ // decode window size
+ int windowSize = -1;
+ if (!singleSegment) {
+ int windowDescriptor = UNSAFE.getByte(inputBase, input++) & 0xFF;
+ int exponent = windowDescriptor >>> 3;
+ int mantissa = windowDescriptor & 0b111;
+
+ int base = 1 << (MIN_WINDOW_LOG + exponent);
+ windowSize = base + (base / 8) * mantissa;
+ }
+
+ // decode dictionary id
+ long dictionaryId = -1;
+ switch (dictionaryDescriptor) {
+ case 1:
+ dictionaryId = UNSAFE.getByte(inputBase, input) & 0xFF;
+ input += SIZE_OF_BYTE;
+ break;
+ case 2:
+ dictionaryId = UNSAFE.getShort(inputBase, input) & 0xFFFF;
+ input += SIZE_OF_SHORT;
+ break;
+ case 3:
+ dictionaryId = UNSAFE.getInt(inputBase, input) & 0xFFFF_FFFFL;
+ input += SIZE_OF_INT;
+ break;
+ }
+ verify(dictionaryId == -1, input, "Custom dictionaries not supported");
+
+ // decode content size
+ long contentSize = -1;
+ switch (contentSizeDescriptor) {
+ case 0:
+ if (singleSegment) {
+ contentSize = UNSAFE.getByte(inputBase, input) & 0xFF;
+ input += SIZE_OF_BYTE;
+ }
+ break;
+ case 1:
+ contentSize = UNSAFE.getShort(inputBase, input) & 0xFFFF;
+ contentSize += 256;
+ input += SIZE_OF_SHORT;
+ break;
+ case 2:
+ contentSize = UNSAFE.getInt(inputBase, input) & 0xFFFF_FFFFL;
+ input += SIZE_OF_INT;
+ break;
+ case 3:
+ contentSize = UNSAFE.getLong(inputBase, input);
+ input += SIZE_OF_LONG;
+ break;
+ }
+
+ boolean hasChecksum = (frameHeaderDescriptor & 0b100) != 0;
+
+ return new FrameHeader(
+ input - inputAddress,
+ windowSize,
+ contentSize,
+ dictionaryId,
+ hasChecksum);
+ }
+
+ public static long getDecompressedSize(final Object inputBase, final long inputAddress, final long inputLimit)
+ {
+ long input = inputAddress;
+ input += verifyMagic(inputBase, input, inputLimit);
+ return readFrameHeader(inputBase, input, inputLimit).contentSize;
+ }
+
+ private static int verifyMagic(Object inputBase, long inputAddress, long inputLimit)
+ {
+ verify(inputLimit - inputAddress >= 4, inputAddress, "Not enough input bytes");
+
+ int magic = UNSAFE.getInt(inputBase, inputAddress);
+ if (magic != MAGIC_NUMBER) {
+ throw new MalformedInputException(inputAddress, "Invalid magic prefix: " + Integer.toHexString(magic));
+ }
+
+ return SIZE_OF_INT;
+ }
+}
diff --git a/core/src/main/java/com/boydti/fawe/object/io/zstd/ZstdInputStream.java b/core/src/main/java/com/boydti/fawe/object/io/zstd/ZstdInputStream.java
new file mode 100644
index 00000000..1f3e94d3
--- /dev/null
+++ b/core/src/main/java/com/boydti/fawe/object/io/zstd/ZstdInputStream.java
@@ -0,0 +1,179 @@
+package com.github.luben.zstd;
+
+import java.io.InputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.lang.IndexOutOfBoundsException;
+
+import com.github.luben.zstd.util.Native;
+import com.github.luben.zstd.Zstd;
+
+/**
+ * InputStream filter that decompresses the data provided
+ * by the underlying InputStream using Zstd compression.
+ *
+ * It does not support mark/reset methods
+ *
+ */
+
+public class ZstdInputStream extends FilterInputStream {
+
+ static {
+ Native.load();
+ }
+
+ // Opaque pointer to Zstd context object
+ private long stream;
+ private long dstPos = 0;
+ private long srcPos = 0;
+ private long srcSize = 0;
+ private byte[] src = null;
+ private static final int srcBuffSize = (int) recommendedDInSize();
+
+ private boolean isContinuous = false;
+ private boolean frameFinished = false;
+ private boolean isClosed = false;
+
+ /* JNI methods */
+ private static native long recommendedDInSize();
+ private static native long recommendedDOutSize();
+ private static native long createDStream();
+ private static native int freeDStream(long stream);
+ private native int initDStream(long stream);
+ private native int decompressStream(long stream, byte[] dst, int dst_size, byte[] src, int src_size);
+
+ // The main constuctor / legacy version dispatcher
+ public ZstdInputStream(InputStream inStream) throws IOException {
+ // FilterInputStream constructor
+ super(inStream);
+
+ // allocate input buffer with max frame header size
+ src = new byte[srcBuffSize];
+ if (src == null) {
+ throw new IOException("Error allocating the input buffer of size " + srcBuffSize);
+ }
+ stream = createDStream();
+ int size = initDStream(stream);
+ if (Zstd.isError(size)) {
+ throw new IOException("Decompression error: " + Zstd.getErrorName(size));
+ }
+ }
+
+ /**
+ * Don't break on unfinished frames
+ *
+ * Use case: decompressing files that are not
+ * yet finished writing and compressing
+ */
+ public ZstdInputStream setContinuous(boolean b) {
+ isContinuous = b;
+ return this;
+ }
+
+ public boolean getContinuous() {
+ return this.isContinuous;
+ }
+
+ public int read(byte[] dst, int offset, int len) throws IOException {
+
+ if (isClosed) {
+ throw new IOException("Stream closed");
+ }
+
+ // guard agains buffer overflows
+ if (offset < 0 || len > dst.length - offset) {
+ throw new IndexOutOfBoundsException("Requested lenght " + len
+ + " from offset " + offset + " in buffer of size " + dst.length);
+ }
+ int dstSize = offset + len;
+ dstPos = offset;
+
+ while (dstPos < dstSize) {
+ if (srcSize - srcPos == 0) {
+ srcSize = in.read(src, 0, srcBuffSize);
+ srcPos = 0;
+ if (srcSize < 0) {
+ srcSize = 0;
+ if (frameFinished) {
+ return -1;
+ } else if (isContinuous) {
+ return (int)(dstPos - offset);
+ } else {
+ throw new IOException("Read error or truncated source");
+ }
+ }
+ frameFinished = false;
+ }
+
+ int size = decompressStream(stream, dst, dstSize, src, (int) srcSize);
+
+ if (Zstd.isError(size)) {
+ throw new IOException("Decompression error: " + Zstd.getErrorName(size));
+ }
+
+ // we have completed a frame
+ if (size == 0) {
+ frameFinished = true;
+ // re-init the codec so it can start decoding next frame
+ size = initDStream(stream);
+ if (Zstd.isError(size)) {
+ throw new IOException("Decompression error: " + Zstd.getErrorName(size));
+ }
+ return (int)(dstPos - offset);
+ }
+ }
+ return len;
+ }
+
+ public int read() throws IOException {
+ byte[] oneByte = new byte[1];
+ int result = read(oneByte, 0, 1);
+ if (result > 0) {
+ return oneByte[0] & 0xff;
+ } else {
+ return result;
+ }
+ }
+
+ public int available() throws IOException {
+ if (isClosed) {
+ throw new IOException("Stream closed");
+ }
+ if (srcSize - srcPos > 0) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+
+ /* we don't support mark/reset */
+ public boolean markSupported() {
+ return false;
+ }
+
+ /* we can skip forward */
+ public long skip(long toSkip) throws IOException {
+ if (isClosed) {
+ throw new IOException("Stream closed");
+ }
+ long skipped = 0;
+ int dstSize = (int) recommendedDOutSize();
+ byte[] dst = new byte[dstSize];
+ while (toSkip > dstSize) {
+ long size = read(dst, 0, dstSize);
+ toSkip -= size;
+ skipped += size;
+ }
+ skipped += read(dst, 0, (int) toSkip);
+ return skipped;
+ }
+
+ public void close() throws IOException {
+ if (isClosed) {
+ return;
+ }
+ freeDStream(stream);
+ in.close();
+ isClosed = true;
+ }
+}
diff --git a/core/src/main/java/com/boydti/fawe/object/mask/AngleMask.java b/core/src/main/java/com/boydti/fawe/object/mask/AngleMask.java
index 57aa4f0a..d192f7d9 100644
--- a/core/src/main/java/com/boydti/fawe/object/mask/AngleMask.java
+++ b/core/src/main/java/com/boydti/fawe/object/mask/AngleMask.java
@@ -56,8 +56,8 @@ public class AngleMask extends SolidBlockMask implements ResettableMask {
try {
int rx = x - cacheBotX;
int rz = z - cacheBotZ;
- int index = rx + (rz << 8);
- if (index < 0 || index >= 65536) {
+ int index;
+ if (((rx + 16) & 0xFF) != rx + 16 || ((rz + 16) & 0xFF) != rz + 16) {
cacheBotX = x - 16;
cacheBotZ = z - 16;
rx = x - cacheBotX;
@@ -68,6 +68,8 @@ public class AngleMask extends SolidBlockMask implements ResettableMask {
} else {
Arrays.fill(cacheHeights, (byte) 0);
}
+ } else {
+ index = rx + (rz << 8);
}
int result = cacheHeights[index] & 0xFF;
if (result == 0) {
diff --git a/core/src/main/java/com/boydti/fawe/object/schematic/FaweFormat.java b/core/src/main/java/com/boydti/fawe/object/schematic/FaweFormat.java
index c9ad20f5..eda2f585 100644
--- a/core/src/main/java/com/boydti/fawe/object/schematic/FaweFormat.java
+++ b/core/src/main/java/com/boydti/fawe/object/schematic/FaweFormat.java
@@ -16,7 +16,6 @@ import com.sk89q.jnbt.ListTag;
import com.sk89q.jnbt.NamedTag;
import com.sk89q.jnbt.StringTag;
import com.sk89q.jnbt.Tag;
-import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.MutableBlockVector;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.WorldEditException;
@@ -272,7 +271,7 @@ public class FaweFormat implements ClipboardReader, ClipboardWriter {
out.writeInt(origin.getBlockZ());
for (Vector pt : clipboard.getRegion()) {
BaseBlock block = clipboard.getBlock(pt);
- if (block == EditSession.nullBlock) {
+ if (block.getId() == 0) {
continue;
}
int x = pt.getBlockX() - min.getBlockX();
@@ -321,7 +320,7 @@ public class FaweFormat implements ClipboardReader, ClipboardWriter {
for (int z = min.getBlockZ(); z <= max.getBlockZ(); z++) {
mutable.mutZ(z);
BaseBlock block = clipboard.getBlock(mutable);
- if (block == EditSession.nullBlock) {
+ if (block.getId() == 0) {
out.writeShort((short) 0);
} else {
out.writeShort((short) FaweCache.getCombined(block));
diff --git a/core/src/main/java/com/boydti/fawe/object/schematic/PNGWriter.java b/core/src/main/java/com/boydti/fawe/object/schematic/PNGWriter.java
index db7fa478..e7cf5f9a 100644
--- a/core/src/main/java/com/boydti/fawe/object/schematic/PNGWriter.java
+++ b/core/src/main/java/com/boydti/fawe/object/schematic/PNGWriter.java
@@ -108,7 +108,7 @@ public class PNGWriter implements ClipboardWriter {
for (int y = y0; y < y0 + height; y++) {
mutable.mutY(y);
BaseBlock block = clipboard.getBlock(mutable);
- if (block == EditSession.nullBlock) {
+ if (block.getId() == 0) {
continue;
}
mutableTop.mutY(y + 1);
diff --git a/core/src/main/java/com/boydti/fawe/object/schematic/Schematic.java b/core/src/main/java/com/boydti/fawe/object/schematic/Schematic.java
index fc647f7f..b1ba505b 100644
--- a/core/src/main/java/com/boydti/fawe/object/schematic/Schematic.java
+++ b/core/src/main/java/com/boydti/fawe/object/schematic/Schematic.java
@@ -53,7 +53,7 @@ public class Schematic {
*/
public Schematic(Region region) {
checkNotNull(region);
- checkNotNull(region.getWorld());
+ checkNotNull(region.getWorld(), "World cannot be null (use the other constructor for the region)");
EditSession session = new EditSessionBuilder(region.getWorld()).allowedRegionsEverywhere().autoQueue(false).build();
this.clipboard = new BlockArrayClipboard(region, ReadOnlyClipboard.of(session, region));
}
@@ -206,7 +206,7 @@ public class Schematic {
@Override
public boolean apply(Vector mutable) throws WorldEditException {
BaseBlock block = clipboard.getBlock(mutable);
- if (block == EditSession.nullBlock && !pasteAir) {
+ if (block.getId() == 0 && !pasteAir) {
return false;
}
extent.setBlock(mutable.getBlockX() + relx, mutable.getBlockY() + rely, mutable.getBlockZ() + relz, block);
diff --git a/core/src/main/java/com/boydti/fawe/regions/FaweMask.java b/core/src/main/java/com/boydti/fawe/regions/FaweMask.java
index 2982fdbc..65a2c991 100644
--- a/core/src/main/java/com/boydti/fawe/regions/FaweMask.java
+++ b/core/src/main/java/com/boydti/fawe/regions/FaweMask.java
@@ -58,8 +58,6 @@ public class FaweMask {
return false;
}
- ;
-
public BlockVector[] getBounds() {
final BlockVector[] BlockVectors = {this.position1, this.position2};
return BlockVectors;
diff --git a/core/src/main/java/com/boydti/fawe/regions/general/plot/CreateFromImage.java b/core/src/main/java/com/boydti/fawe/regions/general/plot/CreateFromImage.java
index 8f582838..01089079 100644
--- a/core/src/main/java/com/boydti/fawe/regions/general/plot/CreateFromImage.java
+++ b/core/src/main/java/com/boydti/fawe/regions/general/plot/CreateFromImage.java
@@ -611,7 +611,7 @@ public class CreateFromImage extends Command {
}
if (arg.startsWith("file://")) {
arg = arg.substring(7);
- File file = MainUtil.getFile(Fawe.imp().getDirectory(), com.boydti.fawe.config.Settings.IMP.PATHS.HEIGHTMAP + File.separator + arg);
+ File file = MainUtil.getFile(MainUtil.getFile(Fawe.imp().getDirectory(), com.boydti.fawe.config.Settings.IMP.PATHS.HEIGHTMAP), arg);
return MainUtil.toRGB(ImageIO.read(file));
}
return null;
diff --git a/core/src/main/java/com/boydti/fawe/util/DocumentationPrinter.java b/core/src/main/java/com/boydti/fawe/util/DocumentationPrinter.java
index aa793b97..8883bf14 100644
--- a/core/src/main/java/com/boydti/fawe/util/DocumentationPrinter.java
+++ b/core/src/main/java/com/boydti/fawe/util/DocumentationPrinter.java
@@ -116,9 +116,9 @@ public final class DocumentationPrinter {
writePermissionsWikiTable(stream, builder, "/", BrushOptionsCommands.class);
writePermissionsWikiTable(stream, builder, "/tool ", ToolCommands.class);
writePermissionsWikiTable(stream, builder, "/brush ", BrushCommands.class);
- writePermissionsWikiTable(stream, builder, "/masks ", MaskCommands.class);
- writePermissionsWikiTable(stream, builder, "/patterns ", PatternCommands.class);
- writePermissionsWikiTable(stream, builder, "/transforms ", TransformCommands.class);
+ writePermissionsWikiTable(stream, builder, "", MaskCommands.class);
+ writePermissionsWikiTable(stream, builder, "", PatternCommands.class);
+ writePermissionsWikiTable(stream, builder, "", TransformCommands.class);
stream.println();
stream.print("#### Uncategorized\n");
stream.append("| Aliases | Permission | flags | Usage |\n");
@@ -151,10 +151,14 @@ public final class DocumentationPrinter {
stream.append("\n");
Command cmd = cls.getAnnotation(Command.class);
if (cmd != null) {
- stream.append(" (" + (cmd.help().isEmpty() ? cmd.desc() : cmd.help()) + ")");
+ if (!cmd.desc().isEmpty()) {
+ stream.append("> (" + (cmd.desc()) + ") \n");
+ }
+ if (!cmd.help().isEmpty()) {
+ stream.append("" + (cmd.help()) + " \n");
+ }
}
stream.append("\n");
- stream.append("\n");
stream.append("---");
stream.append("\n");
stream.append("\n");
diff --git a/core/src/main/java/com/boydti/fawe/util/MainUtil.java b/core/src/main/java/com/boydti/fawe/util/MainUtil.java
index 5dc251bb..f72481de 100644
--- a/core/src/main/java/com/boydti/fawe/util/MainUtil.java
+++ b/core/src/main/java/com/boydti/fawe/util/MainUtil.java
@@ -43,6 +43,7 @@ import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
+import java.net.URLClassLoader;
import java.net.URLConnection;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
@@ -469,6 +470,25 @@ public class MainUtil {
}
}
+ private static final Class[] parameters = new Class[]{URL.class};
+
+ public static void loadURLClasspath(URL u) throws IOException {
+
+ URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
+ Class sysclass = URLClassLoader.class;
+
+ try {
+ Method method = sysclass.getDeclaredMethod("addURL", parameters);
+ method.setAccessible(true);
+ method.invoke(sysloader, new Object[]{u});
+ } catch (Throwable t) {
+ t.printStackTrace();
+ throw new IOException("Error, could not add URL to system classloader");
+ }
+
+ }
+
+
public static File getJarFile() {
try {
return getJarFile(Fawe.class);
@@ -907,7 +927,7 @@ public class MainUtil {
long age = now - file.lastModified();
if (age > timeDiff) {
file.delete();
- BBC.SCHEMATIC_DELETE.send(null, file);
+ BBC.FILE_DELETED.send(null, file);
}
}
});
@@ -923,7 +943,7 @@ public class MainUtil {
deleteDirectory(files[i]);
} else {
file.delete();
- BBC.SCHEMATIC_DELETE.send(null, file);
+ BBC.FILE_DELETED.send(null, file);
}
}
}
diff --git a/core/src/main/java/com/boydti/fawe/util/MemUtil.java b/core/src/main/java/com/boydti/fawe/util/MemUtil.java
index 5550bf2a..4aa7f465 100644
--- a/core/src/main/java/com/boydti/fawe/util/MemUtil.java
+++ b/core/src/main/java/com/boydti/fawe/util/MemUtil.java
@@ -27,6 +27,15 @@ public class MemUtil {
return false;
}
+ public static long getUsedBytes() {
+ long used = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+ return used;
+ }
+
+ public static long getFreeBytes() {
+ return Runtime.getRuntime().maxMemory() - getUsedBytes();
+ }
+
public static int calculateMemory() {
final long heapSize = Runtime.getRuntime().totalMemory();
final long heapMaxSize = Runtime.getRuntime().maxMemory();
diff --git a/core/src/main/java/com/boydti/fawe/util/SetQueue.java b/core/src/main/java/com/boydti/fawe/util/SetQueue.java
index 8bddd1c0..5d4c0968 100644
--- a/core/src/main/java/com/boydti/fawe/util/SetQueue.java
+++ b/core/src/main/java/com/boydti/fawe/util/SetQueue.java
@@ -74,6 +74,7 @@ public class SetQueue {
tasks = new ConcurrentLinkedDeque<>();
activeQueues = new ConcurrentLinkedDeque();
inactiveQueues = new ConcurrentLinkedDeque<>();
+ if (TaskManager.IMP == null) return;
TaskManager.IMP.repeat(new Runnable() {
@Override
public void run() {
@@ -236,6 +237,13 @@ public class SetQueue {
}
public void flush(FaweQueue queue) {
+ int parallelThreads;
+ if (Fawe.get().isMainThread()) {
+ parallelThreads = Settings.IMP.QUEUE.PARALLEL_THREADS;
+ Settings.IMP.QUEUE.PARALLEL_THREADS = 1;
+ } else {
+ parallelThreads = 0;
+ }
try {
queue.startSet(Settings.IMP.QUEUE.PARALLEL_THREADS > 1);
queue.next(Settings.IMP.QUEUE.PARALLEL_THREADS, Long.MAX_VALUE);
@@ -247,6 +255,9 @@ public class SetQueue {
queue.endSet(Settings.IMP.QUEUE.PARALLEL_THREADS > 1);
queue.setStage(QueueStage.NONE);
queue.runTasks();
+ if (parallelThreads != 0) {
+ Settings.IMP.QUEUE.PARALLEL_THREADS = parallelThreads;
+ }
}
}
diff --git a/core/src/main/java/com/boydti/fawe/util/StringMan.java b/core/src/main/java/com/boydti/fawe/util/StringMan.java
index e58132f2..bf7b52cb 100644
--- a/core/src/main/java/com/boydti/fawe/util/StringMan.java
+++ b/core/src/main/java/com/boydti/fawe/util/StringMan.java
@@ -9,6 +9,7 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
+import java.util.function.Function;
public class StringMan {
public static String replaceFromMap(final String string, final Map replacements) {
@@ -32,6 +33,25 @@ public class StringMan {
return sb.toString();
}
+ public static String removeFromSet(final String string, final Collection replacements) {
+ final StringBuilder sb = new StringBuilder(string);
+ int size = string.length();
+ for (final String key : replacements) {
+ if (size == 0) {
+ break;
+ }
+ int start = sb.indexOf(key, 0);
+ while (start > -1) {
+ final int end = start + key.length();
+ final int nextSearchStart = start + 0;
+ sb.delete(start, end);
+ size -= end - start;
+ start = sb.indexOf(key, nextSearchStart);
+ }
+ }
+ return sb.toString();
+ }
+
public static int indexOf(String input, int start, char... values) {
for (int i = start; i < input.length(); i++) {
for (char c : values) {
@@ -262,6 +282,19 @@ public class StringMan {
return p[n];
}
+ public static String join(Collection arr, final String delimiter, Function funx) {
+ final StringBuilder result = new StringBuilder();
+ int i = 0;
+ for (T obj : arr) {
+ if (i > 0) {
+ result.append(delimiter);
+ }
+ result.append(funx.apply(obj));
+ i++;
+ }
+ return result.toString();
+ }
+
public static String join(final Object[] array, final String delimiter) {
final StringBuilder result = new StringBuilder();
for (int i = 0, j = array.length; i < j; i++) {
diff --git a/core/src/main/java/com/boydti/fawe/util/TextureUtil.java b/core/src/main/java/com/boydti/fawe/util/TextureUtil.java
index 787d01c1..a78c0e4c 100644
--- a/core/src/main/java/com/boydti/fawe/util/TextureUtil.java
+++ b/core/src/main/java/com/boydti/fawe/util/TextureUtil.java
@@ -453,6 +453,7 @@ public class TextureUtil {
}
public void loadModTextures() throws IOException {
+ BundledBlockData.getInstance().loadFromResource();
Int2ObjectOpenHashMap colorMap = new Int2ObjectOpenHashMap<>();
Int2ObjectOpenHashMap distanceMap = new Int2ObjectOpenHashMap<>();
Gson gson = new Gson();
diff --git a/core/src/main/java/com/boydti/fawe/util/chat/ChatManager.java b/core/src/main/java/com/boydti/fawe/util/chat/ChatManager.java
new file mode 100644
index 00000000..de847001
--- /dev/null
+++ b/core/src/main/java/com/boydti/fawe/util/chat/ChatManager.java
@@ -0,0 +1,21 @@
+package com.boydti.fawe.util.chat;
+
+import com.boydti.fawe.object.FawePlayer;
+
+public interface ChatManager {
+ T builder();
+
+ void color(Message message, String color);
+
+ void tooltip(Message message, Message... tooltip);
+
+ void command(Message message, String command);
+
+ void text(Message message, String text);
+
+ void send(Message message, FawePlayer player);
+
+ void suggest(Message message, String command);
+
+ void link(Message message, String url);
+}
diff --git a/core/src/main/java/com/boydti/fawe/util/chat/Message.java b/core/src/main/java/com/boydti/fawe/util/chat/Message.java
new file mode 100644
index 00000000..b50db7e8
--- /dev/null
+++ b/core/src/main/java/com/boydti/fawe/util/chat/Message.java
@@ -0,0 +1,130 @@
+package com.boydti.fawe.util.chat;
+
+import com.boydti.fawe.Fawe;
+import com.boydti.fawe.config.BBC;
+import com.boydti.fawe.object.FawePlayer;
+import com.sk89q.worldedit.WorldEdit;
+import com.sk89q.worldedit.extension.platform.Actor;
+import java.util.Objects;
+
+public class Message {
+
+ private Object builder;
+ private boolean active;
+
+ public Message() {
+ try {
+ reset(Fawe.get().getChatManager());
+ } catch (Throwable e) {
+ Fawe.debug("Doesn't support fancy chat for " + Fawe.imp().getPlatform());
+ Fawe.get().setChatManager(new PlainChatManager());
+ reset(Fawe.get().getChatManager());
+ }
+ active = !(Fawe.get().getChatManager() instanceof PlainChatManager);
+ }
+
+ public Message(BBC caption, Object... args) {
+ this(BBC.getPrefix() + caption.format(args));
+ }
+
+ public Message(String text) {
+ this();
+ text(text);
+ }
+
+ public T $(ChatManager manager) {
+ return (T) this.builder;
+ }
+
+ public T reset(ChatManager manager) {
+ return (T) (this.builder = manager.builder());
+ }
+
+ public Message activeText(String text) {
+ if (active) {
+ text(text);
+ }
+ return this;
+ }
+
+ public Message text(BBC caption, Object... args) {
+ return text(caption.format(args));
+ }
+
+ public Message text(Object text) {
+ Fawe.get().getChatManager().text(this, BBC.color(Objects.toString(text)));
+ return this;
+ }
+
+ public Message link(String text) {
+ Fawe.get().getChatManager().link(this, text);
+ return this;
+ }
+
+ public Message tooltip(Message... tooltip) {
+ Fawe.get().getChatManager().tooltip(this, tooltip);
+ return this;
+ }
+
+ public Message tooltip(String tooltip) {
+ return tooltip(new Message(tooltip));
+ }
+
+ public Message command(String command) {
+ Fawe.get().getChatManager().command(this, (WorldEdit.getInstance().getConfiguration().noDoubleSlash ? "" : "/") + command);
+ return this;
+ }
+
+ public Message prefix() {
+ return text(BBC.getPrefix());
+ }
+
+ public Message newline() {
+ return text("\n");
+ }
+
+ public Message cmdTip(String commandAndTooltip) {
+ return tooltip(commandAndTooltip).command(commandAndTooltip);
+ }
+
+ public Message suggestTip(String commandAndTooltip) {
+ return tooltip(commandAndTooltip).suggest(commandAndTooltip);
+ }
+
+ public Message suggest(String command) {
+ Fawe.get().getChatManager().suggest(this, command);
+ return this;
+ }
+
+ public Message color(String color) {
+ Fawe.get().getChatManager().color(this, BBC.color(color));
+ return this;
+ }
+
+ public void send(Actor player) {
+ send(FawePlayer.wrap(player));
+ }
+
+ public void send(FawePlayer player) {
+ Fawe.get().getChatManager().send(this, player);
+ }
+
+ public Message paginate(String baseCommand, int page, int totalPages) {
+ if (!active) {
+ return text(BBC.PAGE_FOOTER.f(baseCommand, page + 1));
+ }
+ if (page < totalPages && page > 1) { // Back | Next
+ this.text("&f<<").command(baseCommand + " " + (page - 1)).text("&8 | ").text("&f>>")
+ .command(baseCommand + " " + (page + 1));
+ } else if (page <= 1 && totalPages > page) { // Next
+ this.text("&8 -").text(" | ").text("&f>>")
+ .command(baseCommand + " " + (page + 1));
+
+ } else if (page == totalPages && totalPages > 1) { // Back
+ this.text("&f<<").command(baseCommand + " " + (page - 1)).text("&8 | ").text("- ");
+ } else {
+ this.text("&8 - | - ");
+ }
+ return this;
+ }
+}
diff --git a/core/src/main/java/com/boydti/fawe/util/chat/PlainChatManager.java b/core/src/main/java/com/boydti/fawe/util/chat/PlainChatManager.java
new file mode 100644
index 00000000..d39fd222
--- /dev/null
+++ b/core/src/main/java/com/boydti/fawe/util/chat/PlainChatManager.java
@@ -0,0 +1,47 @@
+package com.boydti.fawe.util.chat;
+
+
+import com.boydti.fawe.config.BBC;
+import com.boydti.fawe.object.FawePlayer;
+import java.util.ArrayList;
+import java.util.List;
+
+public class PlainChatManager implements ChatManager> {
+
+ @Override
+ public List builder() {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public void color(Message message, String color) {
+ List parts = message.$(this);
+ parts.get(parts.size() - 1).insert(0, color);
+ }
+
+ @Override
+ public void tooltip(Message message, Message... tooltips) {}
+
+ @Override
+ public void command(Message message, String command) {}
+
+ @Override
+ public void text(Message message, String text) {
+ message.$(this).add(new StringBuilder(BBC.color(text)));
+ }
+
+ @Override
+ public void send(Message plotMessage, FawePlayer player) {
+ StringBuilder built = new StringBuilder();
+ for (StringBuilder sb : plotMessage.$(this)) {
+ built.append(sb);
+ }
+ player.sendMessage(built.toString());
+ }
+
+ @Override
+ public void suggest(Message plotMessage, String command) {}
+
+ @Override
+ public void link(Message message, String url) {}
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/sk89q/jnbt/NBTOutputStream.java b/core/src/main/java/com/sk89q/jnbt/NBTOutputStream.java
index 6a416d3f..0146b767 100644
--- a/core/src/main/java/com/sk89q/jnbt/NBTOutputStream.java
+++ b/core/src/main/java/com/sk89q/jnbt/NBTOutputStream.java
@@ -19,8 +19,11 @@
package com.sk89q.jnbt;
+import com.boydti.fawe.object.io.LittleEndianOutputStream;
import java.io.Closeable;
+import java.io.DataOutput;
import java.io.DataOutputStream;
+import java.io.Flushable;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
@@ -42,7 +45,7 @@ public final class NBTOutputStream implements Closeable {
/**
* The output stream.
*/
- private final DataOutputStream os;
+ private DataOutput os;
/**
* Creates a new {@code NBTOutputStream}, which will write data to the
@@ -55,10 +58,20 @@ public final class NBTOutputStream implements Closeable {
this.os = new DataOutputStream(os);
}
- public DataOutputStream getOutputStream() {
+ public NBTOutputStream(DataOutput os) throws IOException {
+ this.os = os;
+ }
+
+ public DataOutput getOutputStream() {
return os;
}
+ public void setLittleEndian() {
+ if (!(os instanceof LittleEndianOutputStream)) {
+ this.os = new LittleEndianOutputStream((OutputStream) os);
+ }
+ }
+
/**
* Writes a tag.
*
@@ -363,7 +376,7 @@ public final class NBTOutputStream implements Closeable {
@Override
public void close() throws IOException {
- os.close();
+ if (os instanceof Closeable) ((Closeable) os).close();
}
/**
@@ -372,7 +385,7 @@ public final class NBTOutputStream implements Closeable {
* @throws IOException
*/
public void flush() throws IOException {
- this.os.flush();
+ if (os instanceof Flushable) ((Flushable) os).flush();
}
public static Class> inject() {
diff --git a/core/src/main/java/com/sk89q/worldedit/EditSession.java b/core/src/main/java/com/sk89q/worldedit/EditSession.java
index 98820bfd..726c2b69 100644
--- a/core/src/main/java/com/sk89q/worldedit/EditSession.java
+++ b/core/src/main/java/com/sk89q/worldedit/EditSession.java
@@ -198,7 +198,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
public static final UUID CONSOLE = UUID.fromString("1-1-3-3-7");
public static final BaseBiome nullBiome = new BaseBiome(0);
- public static final BaseBlock nullBlock = FaweCache.CACHE_BLOCK[0];
+ public static final BaseBlock nullBlock = new BaseBlock(0, 0);
private static final Vector[] recurseDirections = {
PlayerDirection.NORTH.vector(),
PlayerDirection.EAST.vector(),
@@ -565,10 +565,10 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
Fawe.debug("&8 - &7For area restrictions, it is recommended to use the FaweAPI");
Fawe.debug("&8 - &7For block logging, it is recommended to use use BlocksHub");
Fawe.debug("&8 - &7To allow this plugin add it to the FAWE `allowed-plugins` list");
- Fawe.debug("&8 - &7To hide this message set `debug` to false in the config.yml");
+ Fawe.debug("&8 - &7To hide this message set `debug` to false in the FAWE config.yml");
if (toReturn.getClass().getName().contains("CoreProtect")) {
Fawe.debug("Note on CoreProtect: ");
- Fawe.debug(" - If you disable CoreProtect's WorldEdit logger (CP config) it still tries to add it (CP bug?)");
+ Fawe.debug(" - If you disable CP's WE logger (CP config) and this still shows, please update CP");
Fawe.debug(" - Use BlocksHub and set `debug` false in the FAWE config");
}
}
@@ -1148,7 +1148,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
}
@Override
- public boolean setBlock(final Vector position, final BaseBlock block, final boolean ignorePhysics) throws MaxChangedBlocksException {
+ public boolean setBlock(final Vector position, final BaseBlock block, final boolean ignorePhysics) {
return setBlockFast(position, block);
}
@@ -1162,7 +1162,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
}
@SuppressWarnings("deprecation")
- public boolean setBlock(final Vector position, final Pattern pattern) throws MaxChangedBlocksException {
+ public boolean setBlock(final Vector position, final Pattern pattern) {
this.changes++;
try {
return pattern.apply(this.extent, position, position);
@@ -1172,7 +1172,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
}
@SuppressWarnings("deprecation")
- public int setBlocks(final Set vset, final Pattern pattern) throws MaxChangedBlocksException {
+ public int setBlocks(final Set vset, final Pattern pattern) {
RegionVisitor visitor = new RegionVisitor(vset, new BlockReplace(extent, pattern), this);
Operations.completeBlindly(visitor);
changes += visitor.getAffected();
@@ -1189,7 +1189,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @return whether a block was changed
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
- public boolean setChanceBlockIfAir(final Vector position, final BaseBlock block, final double probability) throws MaxChangedBlocksException {
+ public boolean setChanceBlockIfAir(final Vector position, final BaseBlock block, final double probability) {
return (FaweCache.RANDOM.random(65536) <= (probability * 65536)) && this.setBlockIfAir(position, block);
}
@@ -1203,7 +1203,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @deprecated Use your own method
*/
@Deprecated
- public boolean setBlockIfAir(final Vector position, final BaseBlock block) throws MaxChangedBlocksException {
+ public boolean setBlockIfAir(final Vector position, final BaseBlock block) {
return this.getBlock(position).isAir() && this.setBlockFast(position, block);
}
@@ -1450,7 +1450,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
@SuppressWarnings("deprecation")
- public int fillXZ(final Vector origin, final BaseBlock block, final double radius, final int depth, final boolean recursive) throws MaxChangedBlocksException {
+ public int fillXZ(final Vector origin, final BaseBlock block, final double radius, final int depth, final boolean recursive) {
return this.fillXZ(origin, (Pattern) block, radius, depth, recursive);
}
@@ -1466,7 +1466,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
@SuppressWarnings("deprecation")
- public int fillXZ(final Vector origin, final Pattern pattern, final double radius, final int depth, final boolean recursive) throws MaxChangedBlocksException {
+ public int fillXZ(final Vector origin, final Pattern pattern, final double radius, final int depth, final boolean recursive) {
checkNotNull(origin);
checkNotNull(pattern);
checkArgument(radius >= 0, "radius >= 0");
@@ -1494,7 +1494,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
return this.changes = visitor.getAffected();
}
-// public int fillDirection(final Vector origin, PlayerDirection direction, final Pattern pattern, final double radius, final int depth, final boolean recursive) throws MaxChangedBlocksException {
+// public int fillDirection(final Vector origin, PlayerDirection direction, final Pattern pattern, final double radius, final int depth, final boolean recursive) {
// checkNotNull(origin);
// checkNotNull(pattern);
// checkArgument(radius >= 0, "radius >= 0");
@@ -1562,7 +1562,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
@SuppressWarnings("deprecation")
- public int removeAbove(final Vector position, final int apothem, final int height) throws MaxChangedBlocksException {
+ public int removeAbove(final Vector position, final int apothem, final int height) {
checkNotNull(position);
checkArgument(apothem >= 1, "apothem >= 1");
checkArgument(height >= 1, "height >= 1");
@@ -1583,7 +1583,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
@SuppressWarnings("deprecation")
- public int removeBelow(final Vector position, final int apothem, final int height) throws MaxChangedBlocksException {
+ public int removeBelow(final Vector position, final int apothem, final int height) {
checkNotNull(position);
checkArgument(apothem >= 1, "apothem >= 1");
checkArgument(height >= 1, "height >= 1");
@@ -1604,7 +1604,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
@SuppressWarnings("deprecation")
- public int removeNear(final Vector position, final int blockType, final int apothem) throws MaxChangedBlocksException {
+ public int removeNear(final Vector position, final int blockType, final int apothem) {
checkNotNull(position);
checkArgument(apothem >= 1, "apothem >= 1");
@@ -1656,7 +1656,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
@SuppressWarnings("deprecation")
- public int setBlocks(final Region region, final BaseBlock block) throws MaxChangedBlocksException {
+ public int setBlocks(final Region region, final BaseBlock block) {
checkNotNull(region);
checkNotNull(block);
if (canBypassAll(region, false, true) && !block.hasNbtData()) {
@@ -1675,8 +1675,6 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
}
}
}
- } catch (final MaxChangedBlocksException e) {
- throw e;
} catch (final WorldEditException e) {
throw new RuntimeException("Unexpected exception", e);
}
@@ -1692,7 +1690,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
@SuppressWarnings("deprecation")
- public int setBlocks(final Region region, final Pattern pattern) throws MaxChangedBlocksException {
+ public int setBlocks(final Region region, final Pattern pattern) {
checkNotNull(region);
checkNotNull(pattern);
if (pattern instanceof BlockPattern) {
@@ -1718,7 +1716,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
@SuppressWarnings("deprecation")
- public int replaceBlocks(final Region region, final Set filter, final BaseBlock replacement) throws MaxChangedBlocksException {
+ public int replaceBlocks(final Region region, final Set filter, final BaseBlock replacement) {
// if (canBypassAll()) {
// queue.replaceBlocks(regionWrapper, blocks, block);
// return changes = region.getArea();
@@ -1739,7 +1737,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
@SuppressWarnings("deprecation")
- public int replaceBlocks(final Region region, final Set filter, final Pattern pattern) throws MaxChangedBlocksException {
+ public int replaceBlocks(final Region region, final Set filter, final Pattern pattern) {
// if (pattern instanceof BaseBlock) {
// return replaceBlocks(region, filter, ((BaseBlock) pattern));
// }
@@ -1747,7 +1745,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
return this.replaceBlocks(region, mask, pattern);
}
-// public int replaceBlocks(final Region region, final Mask mask, final BaseBlock block) throws MaxChangedBlocksException {
+// public int replaceBlocks(final Region region, final Mask mask, final BaseBlock block) {
// TODO fast replace
// }
@@ -1762,7 +1760,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
@SuppressWarnings("deprecation")
- public int replaceBlocks(final Region region, final Mask mask, final Pattern pattern) throws MaxChangedBlocksException {
+ public int replaceBlocks(final Region region, final Mask mask, final Pattern pattern) {
checkNotNull(region);
checkNotNull(mask);
checkNotNull(pattern);
@@ -1784,7 +1782,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
@SuppressWarnings("deprecation")
- public int center(final Region region, final Pattern pattern) throws MaxChangedBlocksException {
+ public int center(final Region region, final Pattern pattern) {
checkNotNull(region);
checkNotNull(pattern);
@@ -1803,7 +1801,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
@SuppressWarnings("deprecation")
- public int makeCuboidFaces(final Region region, final BaseBlock block) throws MaxChangedBlocksException {
+ public int makeCuboidFaces(final Region region, final BaseBlock block) {
return this.makeCuboidFaces(region, (Pattern) (block));
}
@@ -1816,7 +1814,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
@SuppressWarnings("deprecation")
- public int makeCuboidFaces(final Region region, final Pattern pattern) throws MaxChangedBlocksException {
+ public int makeCuboidFaces(final Region region, final Pattern pattern) {
checkNotNull(region);
checkNotNull(pattern);
@@ -1857,7 +1855,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
@SuppressWarnings("deprecation")
- public int makeCuboidWalls(final Region region, final BaseBlock block) throws MaxChangedBlocksException {
+ public int makeCuboidWalls(final Region region, final BaseBlock block) {
return this.makeCuboidWalls(region, (Pattern) (block));
}
@@ -1871,7 +1869,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
@SuppressWarnings("deprecation")
- public int makeCuboidWalls(final Region region, final Pattern pattern) throws MaxChangedBlocksException {
+ public int makeCuboidWalls(final Region region, final Pattern pattern) {
checkNotNull(region);
checkNotNull(pattern);
@@ -1925,7 +1923,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
@SuppressWarnings("deprecation")
- public int overlayCuboidBlocks(final Region region, final BaseBlock block) throws MaxChangedBlocksException {
+ public int overlayCuboidBlocks(final Region region, final BaseBlock block) {
checkNotNull(block);
return this.overlayCuboidBlocks(region, (Pattern) (block));
}
@@ -1940,7 +1938,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
@SuppressWarnings("deprecation")
- public int overlayCuboidBlocks(final Region region, final Pattern pattern) throws MaxChangedBlocksException {
+ public int overlayCuboidBlocks(final Region region, final Pattern pattern) {
checkNotNull(region);
checkNotNull(pattern);
final BlockReplace replace = new BlockReplace(EditSession.this, pattern);
@@ -1961,7 +1959,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @return number of blocks affected
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
- public int naturalizeCuboidBlocks(final Region region) throws MaxChangedBlocksException {
+ public int naturalizeCuboidBlocks(final Region region) {
checkNotNull(region);
final Naturalizer naturalizer = new Naturalizer(EditSession.this);
final FlatRegion flatRegion = Regions.asFlatRegion(region);
@@ -1980,7 +1978,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @return number of blocks affected
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
- public int stackCuboidRegion(final Region region, final Vector dir, final int count, final boolean copyAir) throws MaxChangedBlocksException {
+ public int stackCuboidRegion(final Region region, final Vector dir, final int count, final boolean copyAir) {
checkNotNull(region);
checkNotNull(dir);
checkArgument(count >= 1, "count >= 1 required");
@@ -2013,7 +2011,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @return number of blocks moved
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
- public int moveRegion(final Region region, final Vector dir, final int distance, final boolean copyAir, final BaseBlock replacement) throws MaxChangedBlocksException {
+ public int moveRegion(final Region region, final Vector dir, final int distance, final boolean copyAir, final BaseBlock replacement) {
checkNotNull(region);
checkNotNull(dir);
checkArgument(distance >= 1, "distance >= 1 required");
@@ -2067,7 +2065,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @return number of blocks moved
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
- public int moveCuboidRegion(final Region region, final Vector dir, final int distance, final boolean copyAir, final BaseBlock replacement) throws MaxChangedBlocksException {
+ public int moveCuboidRegion(final Region region, final Vector dir, final int distance, final boolean copyAir, final BaseBlock replacement) {
return this.moveRegion(region, dir, distance, copyAir, replacement);
}
@@ -2079,7 +2077,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @return number of blocks affected
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
- public int drainArea(final Vector origin, final double radius) throws MaxChangedBlocksException {
+ public int drainArea(final Vector origin, final double radius) {
checkNotNull(origin);
checkArgument(radius >= 0, "radius >= 0 required");
Mask liquidMask;
@@ -2123,7 +2121,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @return number of blocks affected
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
- public int fixLiquid(final Vector origin, final double radius, final int moving, final int stationary) throws MaxChangedBlocksException {
+ public int fixLiquid(final Vector origin, final double radius, final int moving, final int stationary) {
checkNotNull(origin);
checkArgument(radius >= 0, "radius >= 0 required");
// Our origins can only be liquids
@@ -2183,7 +2181,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @return number of blocks changed
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
- public int makeCylinder(final Vector pos, final Pattern block, final double radius, final int height, final boolean filled) throws MaxChangedBlocksException {
+ public int makeCylinder(final Vector pos, final Pattern block, final double radius, final int height, final boolean filled) {
return this.makeCylinder(pos, block, radius, radius, height, filled);
}
@@ -2199,7 +2197,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @return number of blocks changed
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
- public int makeCylinder(Vector pos, final Pattern block, double radiusX, double radiusZ, int height, final boolean filled) throws MaxChangedBlocksException {
+ public int makeCylinder(Vector pos, final Pattern block, double radiusX, double radiusZ, int height, final boolean filled) {
radiusX += 0.5;
radiusZ += 0.5;
@@ -2265,7 +2263,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
return this.changes;
}
- public int makeCircle(Vector pos, final Pattern block, double radiusX, double radiusY, double radiusZ, boolean filled, Vector normal) throws MaxChangedBlocksException {
+ public int makeCircle(Vector pos, final Pattern block, double radiusX, double radiusY, double radiusZ, boolean filled, Vector normal) {
radiusX += 0.5;
radiusY += 0.5;
radiusZ += 0.5;
@@ -2362,7 +2360,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @return number of blocks changed
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
- public int makeSphere(final Vector pos, final Pattern block, final double radius, final boolean filled) throws MaxChangedBlocksException {
+ public int makeSphere(final Vector pos, final Pattern block, final double radius, final boolean filled) {
return this.makeSphere(pos, block, radius, radius, radius, filled);
}
@@ -2378,7 +2376,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @return number of blocks changed
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
- public int makeSphere(final Vector pos, final Pattern block, double radiusX, double radiusY, double radiusZ, final boolean filled) throws MaxChangedBlocksException {
+ public int makeSphere(final Vector pos, final Pattern block, double radiusX, double radiusY, double radiusZ, final boolean filled) {
radiusX += 0.5;
radiusY += 0.5;
radiusZ += 0.5;
@@ -2457,7 +2455,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @return number of blocks changed
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
- public int makePyramid(final Vector position, final Pattern block, int size, final boolean filled) throws MaxChangedBlocksException {
+ public int makePyramid(final Vector position, final Pattern block, int size, final boolean filled) {
final int height = size;
for (int y = 0; y <= height; ++y) {
@@ -2485,7 +2483,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @return number of blocks affected
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
- public int thaw(final Vector position, final double radius) throws MaxChangedBlocksException {
+ public int thaw(final Vector position, final double radius) {
final double radiusSq = radius * radius;
final int ox = position.getBlockX();
@@ -2536,7 +2534,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @return number of blocks affected
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
- public int simulateSnow(final Vector position, final double radius) throws MaxChangedBlocksException {
+ public int simulateSnow(final Vector position, final double radius) {
final double radiusSq = radius * radius;
@@ -2598,7 +2596,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @deprecated Use {@link #green(Vector, double, boolean)}.
*/
@Deprecated
- public int green(final Vector position, final double radius) throws MaxChangedBlocksException {
+ public int green(final Vector position, final double radius) {
return this.green(position, radius, true);
}
@@ -2611,7 +2609,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @return number of blocks affected
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
- public int green(final Vector position, final double radius, final boolean onlyNormalDirt) throws MaxChangedBlocksException {
+ public int green(final Vector position, final double radius, final boolean onlyNormalDirt) {
final double radiusSq = radius * radius;
@@ -2673,7 +2671,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @return number of patches created
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
- public int makePumpkinPatches(final Vector position, final int apothem) throws MaxChangedBlocksException {
+ public int makePumpkinPatches(final Vector position, final int apothem) {
// We want to generate pumpkins
final GardenPatchGenerator generator = new GardenPatchGenerator(EditSession.this);
generator.setPlant(GardenPatchGenerator.getPumpkinPattern());
@@ -2938,7 +2936,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @return number of blocks affected
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
- public int hollowOutRegion(final Region region, final int thickness, final Pattern pattern) throws MaxChangedBlocksException {
+ public int hollowOutRegion(final Region region, final int thickness, final Pattern pattern) {
final Set outside = new LocalBlockVectorSet();
@@ -3010,7 +3008,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
return changes;
}
- public int drawLine(final Pattern pattern, final Vector pos1, final Vector pos2, final double radius, final boolean filled) throws MaxChangedBlocksException {
+ public int drawLine(final Pattern pattern, final Vector pos1, final Vector pos2, final double radius, final boolean filled) {
return drawLine(pattern, pos1, pos2, radius, filled, false);
}
@@ -3025,7 +3023,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @return number of blocks affected
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
- public int drawLine(final Pattern pattern, final Vector pos1, final Vector pos2, final double radius, final boolean filled, boolean flat) throws MaxChangedBlocksException {
+ public int drawLine(final Pattern pattern, final Vector pos1, final Vector pos2, final double radius, final boolean filled, boolean flat) {
LocalBlockVectorSet vset = new LocalBlockVectorSet();
boolean notdrawn = true;
@@ -3098,7 +3096,7 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
* @return number of blocks affected
* @throws MaxChangedBlocksException thrown if too many blocks are changed
*/
- public int drawSpline(final Pattern pattern, final List nodevectors, final double tension, final double bias, final double continuity, final double quality, final double radius, final boolean filled) throws MaxChangedBlocksException {
+ public int drawSpline(final Pattern pattern, final List nodevectors, final double tension, final double bias, final double continuity, final double quality, final double radius, final boolean filled) {
LocalBlockVectorSet vset = new LocalBlockVectorSet();
final List nodes = new ArrayList(nodevectors.size());
@@ -3463,14 +3461,18 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting
}
}
- public boolean generateTree(TreeGenerator.TreeType type, Vector position) throws MaxChangedBlocksException {
+ public boolean generateTree(TreeGenerator.TreeType type, Vector position) {
return generateTree(type, this, position);
}
@Override
- public boolean generateTree(TreeGenerator.TreeType type, EditSession editSession, Vector position) throws MaxChangedBlocksException {
+ public boolean generateTree(TreeGenerator.TreeType type, EditSession editSession, Vector position) {
if (getWorld() != null) {
- return getWorld().generateTree(type, editSession, position);
+ try {
+ return getWorld().generateTree(type, editSession, position);
+ } catch (MaxChangedBlocksException e) {
+ throw new RuntimeException(e);
+ }
}
return false;
}
diff --git a/core/src/main/java/com/sk89q/worldedit/blocks/BaseBlock.java b/core/src/main/java/com/sk89q/worldedit/blocks/BaseBlock.java
index c3f58811..9fe94f31 100644
--- a/core/src/main/java/com/sk89q/worldedit/blocks/BaseBlock.java
+++ b/core/src/main/java/com/sk89q/worldedit/blocks/BaseBlock.java
@@ -33,6 +33,7 @@ import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.world.registry.WorldData;
import java.io.DataInputStream;
+import java.io.DataOutput;
import java.io.IOException;
import java.io.Serializable;
import java.util.Collection;
@@ -141,7 +142,7 @@ public class BaseBlock implements TileEntityBlock, Pattern, Serializable {
this(other.getId(), other.getData(), other.getNbtData());
}
- public final int getCombined() {
+ public int getCombined() {
return FaweCache.getCombined(this);
}
@@ -456,7 +457,7 @@ public class BaseBlock implements TileEntityBlock, Pattern, Serializable {
stream.writeChar(getCombined());
stream.writeBoolean(nbtData != null);
if (nbtData != null) {
- new NBTOutputStream(stream).writeTag(nbtData);
+ new NBTOutputStream((DataOutput) stream).writeTag(nbtData);
}
}
diff --git a/core/src/main/java/com/sk89q/worldedit/blocks/ImmutableBlock.java b/core/src/main/java/com/sk89q/worldedit/blocks/ImmutableBlock.java
index ba7206ca..720103f5 100644
--- a/core/src/main/java/com/sk89q/worldedit/blocks/ImmutableBlock.java
+++ b/core/src/main/java/com/sk89q/worldedit/blocks/ImmutableBlock.java
@@ -3,8 +3,16 @@ package com.sk89q.worldedit.blocks;
import com.sk89q.worldedit.CuboidClipboard;
public class ImmutableBlock extends BaseBlock {
+ private final int combined;
+
public ImmutableBlock(int id, int data) {
super(id, data);
+ this.combined = super.getCombined();
+ }
+
+ @Override
+ public final int getCombined() {
+ return combined;
}
@Override
diff --git a/core/src/main/java/com/sk89q/worldedit/command/BiomeCommands.java b/core/src/main/java/com/sk89q/worldedit/command/BiomeCommands.java
index 895dde8e..0732699f 100644
--- a/core/src/main/java/com/sk89q/worldedit/command/BiomeCommands.java
+++ b/core/src/main/java/com/sk89q/worldedit/command/BiomeCommands.java
@@ -20,8 +20,10 @@
package com.sk89q.worldedit.command;
import com.boydti.fawe.config.BBC;
+import com.boydti.fawe.config.Commands;
import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.object.visitor.Fast2DIterator;
+import com.boydti.fawe.util.chat.Message;
import com.sk89q.minecraft.util.commands.Command;
import com.sk89q.minecraft.util.commands.CommandContext;
import com.sk89q.minecraft.util.commands.CommandPermissions;
@@ -57,16 +59,13 @@ import java.util.Collections;
import java.util.List;
-import static com.google.common.base.Preconditions.checkNotNull;
import static com.sk89q.minecraft.util.commands.Logging.LogMode.REGION;
/**
* Implements biome-related commands such as "/biomelist".
*/
@Command(aliases = {}, desc = "Change, list and inspect biomes")
-public class BiomeCommands {
-
- private final WorldEdit worldEdit;
+public class BiomeCommands extends MethodCommands {
/**
* Create a new instance.
@@ -74,8 +73,7 @@ public class BiomeCommands {
* @param worldEdit reference to WorldEdit
*/
public BiomeCommands(WorldEdit worldEdit) {
- checkNotNull(worldEdit);
- this.worldEdit = worldEdit;
+ super(worldEdit);
}
@Command(
@@ -94,28 +92,31 @@ public class BiomeCommands {
page = 1;
offset = 0;
} else {
- offset = (page - 1) * 19;
+ offset = (page - 1) * 18;
}
BiomeRegistry biomeRegistry = player.getWorld().getWorldData().getBiomeRegistry();
List biomes = biomeRegistry.getBiomes();
int totalPages = biomes.size() / 19 + 1;
- BBC.BIOME_LIST_HEADER.send(player, page, totalPages);
+ Message msg = BBC.BIOME_LIST_HEADER.m(page, totalPages);
+ String setBiome = Commands.getAlias(BiomeCommands.class, "/setbiome");
for (BaseBiome biome : biomes) {
if (offset > 0) {
offset--;
} else {
BiomeData data = biomeRegistry.getData(biome);
if (data != null) {
- player.print(BBC.getPrefix() + " " + data.getName());
- if (++count == 19) {
- break;
- }
+ msg.newline().text(data.getName()).cmdTip(setBiome + " " + data.getName());
} else {
- player.print(BBC.getPrefix() + " #" + biome.getId() + ">");
+ msg.newline().text(" #" + biome.getId() + ">").cmdTip(setBiome + " " + biome.getId());
+ }
+ if (++count == 18) {
+ break;
}
}
}
+ msg.newline().paginate(getCommand().aliases()[0], page, totalPages);
+ msg.send(player);
}
@Command(
diff --git a/core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java b/core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java
index 77e26fc4..f08f8eaa 100644
--- a/core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java
+++ b/core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java
@@ -101,7 +101,7 @@ import java.util.List;
/**
* Commands to set brush shape.
*/
-@Command(aliases = {"brush", "br", "/b"},
+@Command(aliases = {"brush", "br", "tool"},
desc = "Commands to build and draw from far away. [More Info](https://git.io/vSPYf)"
)
public class BrushCommands extends MethodCommands {
@@ -669,7 +669,7 @@ public class BrushCommands extends MethodCommands {
" - The `-r` flag enables random off-axis rotation\n" +
" - The `-l` flag will work on snow layers\n" +
" - The `-s` flag disables smoothing",
- desc = "This brush raises and lowers land towards the clicked point\n",
+ desc = "This brush raises or lowers land towards the clicked point",
min = 1,
max = 4
)
diff --git a/core/src/main/java/com/sk89q/worldedit/command/BrushOptionsCommands.java b/core/src/main/java/com/sk89q/worldedit/command/BrushOptionsCommands.java
index f24e16b4..98b61446 100644
--- a/core/src/main/java/com/sk89q/worldedit/command/BrushOptionsCommands.java
+++ b/core/src/main/java/com/sk89q/worldedit/command/BrushOptionsCommands.java
@@ -54,9 +54,10 @@ public class BrushOptionsCommands extends MethodCommands {
}
@Command(
- aliases = {"/savebrush"},
+ aliases = {"savebrush", "save"},
usage = "[name]",
- desc = "Save your current brush\n" +
+ desc = "Save your current brush",
+ help = "Save your current brush\n" +
"use the -g flag to save globally",
min = 1
)
@@ -91,7 +92,7 @@ public class BrushOptionsCommands extends MethodCommands {
}
@Command(
- aliases = {"/loadbrush"},
+ aliases = {"loadbrush", "load"},
desc = "load a brush",
usage = "[name]",
min = 1
@@ -281,12 +282,16 @@ public class BrushOptionsCommands extends MethodCommands {
@Command(
aliases = {"visualize", "visual", "vis"},
- usage = "[mode]",
+ usage = "[mode=0]",
desc = "Toggle between different visualization modes",
+ help = "Toggle between different visualization modes\n" +
+ "0 = No visualization\n" +
+ "1 = Single block at target position\n" +
+ "2 = Glass showing what blocks will be changed",
min = 0,
max = 1
)
- public void visual(Player player, LocalSession session, @Optional("0") int mode) throws WorldEditException {
+ public void visual(Player player, LocalSession session, int mode) throws WorldEditException {
BrushTool tool = session.getBrushTool(player, false);
if (tool == null) {
BBC.BRUSH_NONE.send(player);
diff --git a/core/src/main/java/com/sk89q/worldedit/command/MaskCommands.java b/core/src/main/java/com/sk89q/worldedit/command/MaskCommands.java
index dda73a78..f662207a 100644
--- a/core/src/main/java/com/sk89q/worldedit/command/MaskCommands.java
+++ b/core/src/main/java/com/sk89q/worldedit/command/MaskCommands.java
@@ -46,7 +46,13 @@ import com.sk89q.worldedit.util.command.parametric.Optional;
import com.sk89q.worldedit.world.biome.BaseBiome;
@Command(aliases = {"masks"},
- desc = "Help for the various masks. [More Info](https://git.io/v9r4K)"
+ desc = "Help for the various masks. [More Info](https://git.io/v9r4K)",
+ help = "Masks determine if a block can be placed\n" +
+ " - Use [brackets] for arguments\n" +
+ " - Use , to OR multiple\n" +
+ " - Use & to AND multiple\n" +
+ "e.g. >[stone,dirt],#light[0][5],$jungle\n" +
+ "More Info: https://git.io/v9r4K"
)
public class MaskCommands extends MethodCommands {
public MaskCommands(WorldEdit worldEdit) {
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 a457981e..b00ad7d7 100644
--- a/core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java
+++ b/core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java
@@ -19,8 +19,10 @@
package com.sk89q.worldedit.command;
+import com.boydti.fawe.Fawe;
import com.boydti.fawe.config.BBC;
import com.boydti.fawe.config.Settings;
+import com.boydti.fawe.object.clipboard.ClipboardRemapper;
import com.boydti.fawe.object.clipboard.MultiClipboardHolder;
import com.boydti.fawe.object.schematic.StructureFormat;
import com.boydti.fawe.util.MainUtil;
@@ -115,6 +117,23 @@ public class SchematicCommands {
}
}
+ @Command(
+ aliases = {"remap"},
+ help = "Remap a clipboard between MCPE/PC values\n",
+ desc = "Remap a clipboard between MCPE/PC values\n"
+ )
+ @Deprecated
+ @CommandPermissions({"worldedit.schematic.remap"})
+ public void remap(final Player player, final LocalSession session) throws WorldEditException {
+ ClipboardHolder holder = session.getClipboard();
+ Clipboard clipboard = holder.getClipboard();
+ if (Fawe.imp().getPlatform().equalsIgnoreCase("nukkit")) {
+ new ClipboardRemapper(ClipboardRemapper.RemapPlatform.PC, ClipboardRemapper.RemapPlatform.PE).apply(clipboard);
+ } else {
+ new ClipboardRemapper(ClipboardRemapper.RemapPlatform.PE, ClipboardRemapper.RemapPlatform.PC).apply(clipboard);
+ }
+ }
+
@Command(aliases = {"load"}, usage = "[] ", desc = "Load a schematic into your clipboard")
@Deprecated
@CommandPermissions({"worldedit.clipboard.load", "worldedit.schematic.load", "worldedit.schematic.upload"})
diff --git a/core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java b/core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java
index 08c99157..0d8c23e8 100644
--- a/core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java
+++ b/core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java
@@ -292,7 +292,7 @@ public class SelectionCommands {
session.setToolControl(!session.isToolControlEnabled());
if (session.isToolControlEnabled()) {
- BBC.SELECTION_WAND_ENABLE.send(player);
+ BBC.SELECTION_WAND_ENABLE.m().send(player);
} else {
BBC.SELECTION_WAND_DISABLE.send(player);
}
diff --git a/core/src/main/java/com/sk89q/worldedit/command/TransformCommands.java b/core/src/main/java/com/sk89q/worldedit/command/TransformCommands.java
index abba660c..86b4ba68 100644
--- a/core/src/main/java/com/sk89q/worldedit/command/TransformCommands.java
+++ b/core/src/main/java/com/sk89q/worldedit/command/TransformCommands.java
@@ -22,7 +22,12 @@ import com.sk89q.worldedit.util.command.parametric.Optional;
import java.util.Set;
@Command(aliases = {"transforms"},
- desc = "Help for the various transforms. [More Info](https://git.io/v9KHO)"
+ desc = "Help for the various transforms. [More Info](https://git.io/v9KHO)",
+ help = "Transforms modify how a block is placed\n" +
+ " - Use [brackets] for arguments\n" +
+ " - Use , to OR multiple\n" +
+ " - Use & to AND multiple\n" +
+ "More Info: https://git.io/v9KHO"
)
public class TransformCommands extends MethodCommands {
public TransformCommands(WorldEdit worldEdit) {
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 8108a3b9..c28e3ef1 100644
--- a/core/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java
+++ b/core/src/main/java/com/sk89q/worldedit/command/UtilityCommands.java
@@ -20,10 +20,12 @@
package com.sk89q.worldedit.command;
import com.boydti.fawe.config.BBC;
+import com.boydti.fawe.config.Commands;
import com.boydti.fawe.object.FaweLimit;
import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.util.MathMan;
import com.boydti.fawe.util.StringMan;
+import com.boydti.fawe.util.chat.Message;
import com.google.common.base.Joiner;
import com.sk89q.minecraft.util.commands.Command;
import com.sk89q.minecraft.util.commands.CommandContext;
@@ -76,7 +78,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
-import java.util.LinkedHashSet;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -134,7 +136,7 @@ public class UtilityCommands extends MethodCommands {
public void fillr(Player player, LocalSession session, EditSession editSession, Pattern pattern, double radius, @Optional("1") double depth) throws WorldEditException {
worldEdit.checkMaxRadius(radius);
Vector pos = session.getPlacementPosition(player);
- int affected = 0;
+ int affected;
if (pattern instanceof BaseBlock) {
affected = editSession.fillXZ(pos, ((BaseBlock) pattern), radius, (int) depth, true);
} else {
@@ -614,7 +616,7 @@ public class UtilityCommands extends MethodCommands {
}
File[] files = new File[fileList.size()];
fileList.toArray(files);
- final int perPage = actor instanceof Player ? 8 : 20; // More pages for console
+ final int perPage = actor instanceof Player ? 12 : 20; // More pages for console
int pageCount = (files.length + perPage - 1) / perPage;
if (page < 1) {
BBC.SCHEMATIC_PAGE.send(actor, ">0");
@@ -722,7 +724,7 @@ public class UtilityCommands extends MethodCommands {
int page = -1;
String category = null;
- final int perPage = actor instanceof Player ? 8 : 20; // More pages for console
+ final int perPage = actor instanceof Player ? 12 : 20; // More pages for console
int effectiveLength = args.argsLength();
// Detect page from args
@@ -738,6 +740,8 @@ public class UtilityCommands extends MethodCommands {
}
} catch (NumberFormatException ignored) {
}
+ String baseCommand = (prefix.equals("/") ? Commands.getAlias(UtilityCommands.class, "/help") : prefix);
+ if (effectiveLength > 0) baseCommand += " " + args.getString(0, effectiveLength - 1);
boolean isRootLevel = true;
List visited = new ArrayList();
@@ -748,10 +752,11 @@ public class UtilityCommands extends MethodCommands {
// Get a list of aliases
List aliases = new ArrayList(dispatcher.getCommands());
+ List prefixes = Collections.nCopies(aliases.size(), "");
// Group by callable
if (page == -1 || effectiveLength > 0) {
- Map> grouped = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ Map> grouped = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
for (CommandMapping mapping : aliases) {
CommandCallable c = mapping.getCallable();
String group;
@@ -773,20 +778,23 @@ public class UtilityCommands extends MethodCommands {
}
group = group.replace("/", "");
group = StringMan.toProperCase(group);
- Set queue = grouped.get(group);
+ Map queue = grouped.get(group);
if (queue == null) {
- queue = new LinkedHashSet<>();
+ queue = new LinkedHashMap<>();
grouped.put(group, queue);
}
if (c instanceof Dispatcher) {
- queue.addAll(((Dispatcher) c).getCommands());
+ for (CommandMapping m : ((Dispatcher) c).getCommands()) {
+ queue.put(m, mapping.getPrimaryAlias() + " ");
+ }
} else {
- queue.add(mapping);
+ // Sub commands get priority
+ queue.putIfAbsent(mapping, "");
}
}
if (effectiveLength > 0) {
String cat = args.getString(0);
- Set mappings = effectiveLength == 1 ? grouped.get(cat) : null;
+ Map mappings = effectiveLength == 1 ? grouped.get(cat) : null;
if (mappings == null) {
// Drill down to the command
for (int i = 0; i < effectiveLength; i++) {
@@ -852,28 +860,37 @@ public class UtilityCommands extends MethodCommands {
}
}
if (!(callable instanceof Dispatcher)) {
+ // TODO interactive box
actor.printRaw(BBC.getPrefix() + ColorCodeBuilder.asColorCodes(new CommandUsageBox(callable, Joiner.on(" ").join(visited))));
return;
}
dispatcher = (Dispatcher) callable;
aliases = new ArrayList(dispatcher.getCommands());
+ prefixes = Collections.nCopies(aliases.size(), "");
} else {
- aliases = new ArrayList<>(mappings);
- visited.add(cat);
+ aliases = new ArrayList<>();
+ prefixes = new ArrayList<>();
+ for (Map.Entry entry : mappings.entrySet()) {
+ aliases.add(entry.getKey());
+ prefixes.add(entry.getValue());
+ }
}
page = Math.max(0, page);
} else if (grouped.size() > 1) {
- StringBuilder message = new StringBuilder();
- message.append(BBC.getPrefix() + BBC.HELP_HEADER_CATEGORIES.s() + "\n");
- StringBuilder builder = new StringBuilder();
+ Message msg = new Message();
+ msg.prefix().text(BBC.HELP_HEADER_CATEGORIES).newline();
boolean first = true;
- for (Map.Entry> entry : grouped.entrySet()) {
- String s1 = "&a//help " + entry.getKey();
+ for (Map.Entry> entry : grouped.entrySet()) {
+ String s1 = Commands.getAlias(UtilityCommands.class, "/help") + " " + entry.getKey();
String s2 = entry.getValue().size() + "";
- message.append(BBC.HELP_ITEM_ALLOWED.format(s1, s2) + "\n");
+ msg.text(BBC.HELP_ITEM_ALLOWED, "&a" + s1, s2);
+ msg.tooltip(StringMan.join(entry.getValue().keySet(), ", ", cm -> cm.getPrimaryAlias()));
+ msg.command(s1);
+ msg.newline();
}
- message.append(BBC.HELP_HEADER_FOOTER.s());
- actor.print(BBC.color(message.toString()));
+ msg.text(BBC.HELP_FOOTER).link("https://git.io/vSKE5").newline();
+ msg.paginate(baseCommand, 0, 1);
+ msg.send(actor);
return;
}
}
@@ -886,20 +903,24 @@ public class UtilityCommands extends MethodCommands {
int pageTotal = (int) Math.ceil(aliases.size() / (double) perPage);
// Box
- StringBuilder message = new StringBuilder();
+ Message msg = new Message();
if (offset >= aliases.size()) {
- message.append("&c").append(String.format("There is no page %d (total number of pages is %d).", page + 1, pageTotal));
+ msg.text("&c").text(String.format("There is no page %d (total number of pages is %d).", page + 1, pageTotal));
} else {
- message.append(BBC.getPrefix() + BBC.HELP_HEADER.format(page + 1, pageTotal) + "\n");
- List list = aliases.subList(offset, Math.min(offset + perPage, aliases.size()));
+ msg.prefix().text(BBC.HELP_HEADER, page + 1, pageTotal).newline();
+ int end = Math.min(offset + perPage, aliases.size());
+ List subAliases = aliases.subList(offset, end);
+ List subPrefixes = prefixes.subList(offset, end);
boolean first = true;
// Add each command
- for (CommandMapping mapping : list) {
- CommandCallable c = mapping.getCallable();
+ for (int i = 0; i < subAliases.size(); i++) {
StringBuilder s1 = new StringBuilder();
s1.append(prefix);
+ s1.append(subPrefixes.get(i));
+ CommandMapping mapping = subAliases.get(i);
+ CommandCallable c = mapping.getCallable();
if (!visited.isEmpty()) {
s1.append(Joiner.on(" ").join(visited));
s1.append(" ");
@@ -907,14 +928,21 @@ public class UtilityCommands extends MethodCommands {
s1.append(mapping.getPrimaryAlias());
String s2 = mapping.getDescription().getDescription();
if (c.testPermission(locals)) {
- message.append(BBC.HELP_ITEM_ALLOWED.format(s1, s2) + "\n");
+ // TODO interactive
+ // Hover -> command help
+ // Click -> Suggest command
+ msg.text(BBC.HELP_ITEM_ALLOWED, s1, s2);
+ msg.newline();
} else {
- message.append(BBC.HELP_ITEM_DENIED.format(s1, s2) + "\n");
+ msg.text(BBC.HELP_ITEM_DENIED, s1, s2).newline();
}
}
- message.append(BBC.HELP_HEADER_FOOTER.f());
+ if (args.argsLength() == 0) {
+ msg.text(BBC.HELP_FOOTER).newline();
+ }
+ msg.paginate(baseCommand, page + 1, pageTotal);
}
- actor.print(BBC.color(message.toString()));
+ msg.send(actor);
}
} else {
actor.printRaw(BBC.getPrefix() + ColorCodeBuilder.asColorCodes(new CommandUsageBox(callable, Joiner.on(" ").join(visited))));
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 d69f3651..5994b8e5 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
@@ -103,12 +103,13 @@ public enum ClipboardFormat {
@Override
public ClipboardWriter getWriter(OutputStream outputStream) throws IOException {
- PGZIPOutputStream gzip;
+ OutputStream gzip;
if (outputStream instanceof PGZIPOutputStream || outputStream instanceof GZIPOutputStream) {
- gzip = (PGZIPOutputStream) outputStream;
+ gzip = outputStream;
} else {
outputStream = new BufferedOutputStream(outputStream);
- gzip = new PGZIPOutputStream(outputStream);
+ PGZIPOutputStream pigz = new PGZIPOutputStream(outputStream);
+ gzip = pigz;
}
NBTOutputStream nbtStream = new NBTOutputStream(new BufferedOutputStream(gzip));
return new SchematicWriter(nbtStream);
@@ -163,7 +164,8 @@ public enum ClipboardFormat {
if (outputStream instanceof PGZIPOutputStream || outputStream instanceof GZIPOutputStream) {
gzip = outputStream;
} else {
- gzip = new PGZIPOutputStream(outputStream);
+ PGZIPOutputStream pigz = new PGZIPOutputStream(outputStream);
+ gzip = pigz;
}
NBTOutputStream nbtStream = new NBTOutputStream(new BufferedOutputStream(gzip));
return new StructureFormat(nbtStream);
diff --git a/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SchematicWriter.java b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SchematicWriter.java
index ea02abb7..fd19b5d1 100644
--- a/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SchematicWriter.java
+++ b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SchematicWriter.java
@@ -1,5 +1,6 @@
package com.sk89q.worldedit.extent.clipboard.io;
+import com.boydti.fawe.Fawe;
import com.boydti.fawe.FaweCache;
import com.boydti.fawe.jnbt.NBTStreamer;
import com.boydti.fawe.object.clipboard.FaweClipboard;
@@ -25,7 +26,7 @@ import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.regions.Region;
import com.sk89q.worldedit.util.Location;
import com.sk89q.worldedit.world.registry.WorldData;
-import java.io.DataOutputStream;
+import java.io.DataOutput;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
@@ -88,7 +89,7 @@ public class SchematicWriter implements ClipboardWriter {
blockData[index] = (byte) block.getData();
if (id > 255) {
if (addBlocks == null) { // Lazily create section
- addBlocks = new byte[(blocks.length >> 1) + 1];
+ addBlocks = new byte[((blocks.length + 1) >> 1)];
}
addBlocks[index >> 1] = (byte) (((index & 1) == 0) ? addBlocks[index >> 1] & 0xF0 | (id >> 8) & 0xF : addBlocks[index >> 1] & 0xF | ((id >> 8) & 0xF) << 4);
}
@@ -132,7 +133,7 @@ public class SchematicWriter implements ClipboardWriter {
if (length > MAX_SIZE) {
throw new IllegalArgumentException("Length of region too large for a .schematic");
}
- final DataOutputStream rawStream = outputStream.getOutputStream();
+ final DataOutput rawStream = outputStream.getOutputStream();
outputStream.writeLazyCompoundTag("Schematic", new NBTOutputStream.LazyWrite() {
private boolean hasAdd = false;
private boolean hasTile = false;
@@ -152,6 +153,7 @@ public class SchematicWriter implements ClipboardWriter {
out.writeNamedTag("WEOffsetX", (offset.getBlockX()));
out.writeNamedTag("WEOffsetY", (offset.getBlockY()));
out.writeNamedTag("WEOffsetZ", (offset.getBlockZ()));
+ out.writeNamedTag("Platform", Fawe.imp().getPlatform());
out.writeNamedTagName("Blocks", NBTConstants.TYPE_BYTE_ARRAY);
out.getOutputStream().writeInt(volume);
@@ -196,17 +198,29 @@ public class SchematicWriter implements ClipboardWriter {
if (hasAdd) {
out.writeNamedTagName("AddBlocks", NBTConstants.TYPE_BYTE_ARRAY);
- out.getOutputStream().writeInt(volume);
+ int addLength = (volume + 1) >> 1;
+ out.getOutputStream().writeInt(addLength);
+
+ final int[] lastAdd = new int[1];
+ final boolean[] write = new boolean[1];
+
clipboard.IMP.streamIds(new NBTStreamer.ByteReader() {
@Override
public void run(int index, int byteValue) {
- try {
- rawStream.writeByte(byteValue >> 8);
- } catch (IOException e) {
- e.printStackTrace();
+ if (write[0] ^= true) {
+ try {
+ rawStream.write(((byteValue >> 8) << 4) + (lastAdd[0]));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ } else {
+ lastAdd[0] = byteValue >> 8;
}
}
});
+ if (write[0]) {
+ rawStream.write(lastAdd[0]);
+ }
}
if (hasTile) {
@@ -281,6 +295,7 @@ public class SchematicWriter implements ClipboardWriter {
schematic.put("WEOffsetX", new IntTag(offset.getBlockX()));
schematic.put("WEOffsetY", new IntTag(offset.getBlockY()));
schematic.put("WEOffsetZ", new IntTag(offset.getBlockZ()));
+ schematic.put("Platform", new StringTag(Fawe.imp().getPlatform()));
final byte[] blocks = new byte[width * height * length];
byte[] addBlocks;
diff --git a/core/src/main/java/com/sk89q/worldedit/regions/selector/CuboidRegionSelector.java b/core/src/main/java/com/sk89q/worldedit/regions/selector/CuboidRegionSelector.java
index 0826f268..eeb3bac7 100644
--- a/core/src/main/java/com/sk89q/worldedit/regions/selector/CuboidRegionSelector.java
+++ b/core/src/main/java/com/sk89q/worldedit/regions/selector/CuboidRegionSelector.java
@@ -20,10 +20,13 @@
package com.sk89q.worldedit.regions.selector;
import com.boydti.fawe.config.BBC;
+import com.boydti.fawe.config.Commands;
+import com.boydti.fawe.util.chat.Message;
import com.sk89q.worldedit.BlockVector;
import com.sk89q.worldedit.IncompleteRegionException;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.Vector;
+import com.sk89q.worldedit.command.SelectionCommands;
import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.internal.cui.CUIRegion;
import com.sk89q.worldedit.internal.cui.SelectionPointEvent;
@@ -152,11 +155,14 @@ public class CuboidRegionSelector extends com.sk89q.worldedit.regions.CuboidRegi
checkNotNull(session);
checkNotNull(pos);
+ Message msg;
if (position1 != null && position2 != null) {
- BBC.SELECTOR_CUBOID_POS1.send(player, position1, "(" + region.getArea() + ")");
+ msg = BBC.SELECTOR_CUBOID_POS1.m(position1, "(" + region.getArea() + ")");
} else {
- BBC.SELECTOR_CUBOID_POS1.send(player, position1, "");
+ msg = BBC.SELECTOR_CUBOID_POS1.m(position1, "");
}
+ String cmd = Commands.getAlias(SelectionCommands.class, "/pos1") + " " + pos.getBlockX() + "," + pos.getBlockY() + "," + pos.getBlockZ();
+ msg.suggestTip(cmd).send(player);
session.dispatchCUIEvent(player, new SelectionPointEvent(0, pos, getArea()));
}
@@ -167,11 +173,14 @@ public class CuboidRegionSelector extends com.sk89q.worldedit.regions.CuboidRegi
checkNotNull(session);
checkNotNull(pos);
+ Message msg;
if (position1 != null && position2 != null) {
- BBC.SELECTOR_CUBOID_POS2.send(player, position2, "(" + region.getArea() + ")");
+ msg = BBC.SELECTOR_CUBOID_POS2.m(position2, "(" + region.getArea() + ")");
} else {
- BBC.SELECTOR_CUBOID_POS2.send(player, position2, "");
+ msg = BBC.SELECTOR_CUBOID_POS2.m(position2, "");
}
+ String cmd = Commands.getAlias(SelectionCommands.class, "/pos2") + " " + pos.getBlockX() + "," + pos.getBlockY() + "," + pos.getBlockZ();
+ msg.suggestTip(cmd).send(player);
session.dispatchCUIEvent(player, new SelectionPointEvent(1, pos, getArea()));
}
diff --git a/core/src/main/java/com/sk89q/worldedit/util/command/SimpleCommandMapping.java b/core/src/main/java/com/sk89q/worldedit/util/command/SimpleCommandMapping.java
new file mode 100644
index 00000000..903b88de
--- /dev/null
+++ b/core/src/main/java/com/sk89q/worldedit/util/command/SimpleCommandMapping.java
@@ -0,0 +1,102 @@
+/*
+ * WorldEdit, a Minecraft world manipulation toolkit
+ * Copyright (C) sk89q
+ * Copyright (C) WorldEdit team and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.util.command;
+
+import com.sk89q.worldedit.util.command.parametric.ParametricCallable;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+
+/**
+ * Tracks a command registration.
+ */
+public class SimpleCommandMapping implements CommandMapping {
+
+ private final String[] aliases;
+ private final CommandCallable callable;
+
+ /**
+ * Create a new instance.
+ *
+ * @param callable the command callable
+ * @param alias a list of all aliases, where the first one is the primary one
+ */
+ public SimpleCommandMapping(CommandCallable callable, String... alias) {
+ super();
+ this.aliases = alias;
+ this.callable = callable;
+ }
+
+ @Override
+ public String getPrimaryAlias() {
+ return aliases[0];
+ }
+
+ @Override
+ public String[] getAllAliases() {
+ return aliases;
+ }
+
+ @Override
+ public CommandCallable getCallable() {
+ return callable;
+ }
+
+ @Override
+ public Description getDescription() {
+ return getCallable().getDescription();
+ }
+
+ @Override
+ public int hashCode() {
+ return getPrimaryAlias().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || !(obj instanceof CommandMapping)) {
+ return false;
+ }
+ CommandMapping other = (CommandMapping) obj;
+ if (other.getCallable() != getCallable()) {
+ if (other.getCallable().getClass() != getCallable().getClass() || !(getCallable() instanceof ParametricCallable)) {
+ return false;
+ }
+ Method oMeth = ((ParametricCallable) other.getCallable()).getMethod();
+ Method meth = ((ParametricCallable) getCallable()).getMethod();
+ if (!oMeth.equals(meth)) {
+ return false;
+ }
+ }
+ return other.getPrimaryAlias().equals(getPrimaryAlias());
+ }
+
+ @Override
+ public String toString() {
+ return "CommandMapping{" +
+ "aliases=" + Arrays.toString(aliases) +
+ ", callable=" + callable +
+ '}';
+ }
+
+ public static Class> inject() {
+ return SimpleCommandMapping.class;
+ }
+
+}
diff --git a/core/src/main/resources/de/message.yml b/core/src/main/resources/de/message.yml
index aa1ffe90..1343094b 100644
--- a/core/src/main/resources/de/message.yml
+++ b/core/src/main/resources/de/message.yml
@@ -1,5 +1,6 @@
info:
prefix: '&4&lFAWE:&f&7'
+ file_deleted: '%s0 wurde gelöscht.'
schematic_pasting: '&7Die Schematic wird eingefügt. Dies kann nicht rückgängig gemacht
werden.'
updated_lighting_selection: '&7Das Licht wird in %s0 Chunks aktualisiert.
@@ -183,7 +184,6 @@ worldedit:
tool_radius_error: 'Maximal erlaubter Pinsel Radius: %s0.'
superpickaxe_area_enabled: 'Modus geändert. Linksklick mit einer Spitzhacke. // zum deaktivieren.'
schematic:
- schematic_delete: 'Schematic %s0 wurde gelöscht.'
schematic_format: 'Verfügbare Zwischenablage Formate (Name: Suche Namen)'
schematic_loaded: 'Schematic %s0 geladen. Platziere sie mit //paste'
schematic_saved: 'Schematic %s0 gespeichert.'
diff --git a/core/src/main/resources/ru/message.yml b/core/src/main/resources/ru/message.yml
index 97a44176..1e910110 100644
--- a/core/src/main/resources/ru/message.yml
+++ b/core/src/main/resources/ru/message.yml
@@ -1,5 +1,6 @@
info:
prefix: '&7'
+ file_deleted: '%s0 был удален.'
schematic_pasting: '&7Вставка схематического файла. Это не может быть отменено.'
lighting_propogate_selection: '&7Освещение было распространено в %s0 чанках. (Примечание:
Для удаления освещения используйте //removelight)'
@@ -189,7 +190,6 @@ worldedit:
tool_radius_error: 'Максимально допустимый радиус кисти: %s0.'
superpickaxe_area_enabled: Режим изменен. Щелкните левой кнопкой мыши с киркой. // для отключения.
schematic:
- schematic_delete: '%s0 был удален.'
schematic_format: 'Доступные форматы буфера обмена (Имя: Имена поиска)'
schematic_loaded: '%s0 загружен. Вставить его //paste'
schematic_saved: '%s0 сохранен.'
diff --git a/forge112/build.gradle b/forge112/build.gradle
new file mode 100644
index 00000000..db1316d8
--- /dev/null
+++ b/forge112/build.gradle
@@ -0,0 +1,93 @@
+buildscript {
+ repositories {
+ jcenter()
+ maven {
+ name = "forge"
+ url = "http://files.minecraftforge.net/maven"
+ }
+ maven {url = "https://oss.sonatype.org/content/repositories/snapshots/"}
+ }
+ dependencies {
+ classpath 'net.minecraftforge.gradle:ForgeGradle:2.3-SNAPSHOT'
+ }
+}
+
+apply plugin: 'net.minecraftforge.gradle.forge'
+apply plugin: 'com.github.johnrengelman.shadow'
+
+dependencies {
+ compile project(':core')
+ compile 'org.spongepowered:spongeapi:3.1.0-SNAPSHOT'
+ compile 'com.sk89q.worldedit:worldedit-forge-mc1.8.9:6.1.1'
+}
+
+sourceCompatibility = 1.8
+targetCompatibility = 1.8
+
+repositories {
+ maven {
+ name = 'forge'
+ url = 'http://files.minecraftforge.net/maven'
+ }
+ maven {
+ name = "Sponge"
+ url = "https://repo.spongepowered.org/maven"
+ }
+ maven {
+ name = "Sponge Metrics"
+ url = "http://repo.mcstats.org/content/repositories/releases/"
+ }
+}
+minecraft {
+ version = "1.12-14.21.1.2410"
+ mappings = "snapshot_20170713"
+ runDir = 'run'
+}
+
+project.archivesBaseName = "${project.archivesBaseName}-mc${minecraft.version}"
+
+processResources {
+ from(sourceSets.main.resources.srcDirs) {
+ expand 'version': project.version,
+ 'mcVersion': project.minecraft.version
+ exclude 'mcmod.info'
+ }
+}
+
+shadowJar {
+ relocate 'org.yaml.snakeyaml', 'com.boydti.fawe.yaml'
+ dependencies {
+ include(dependency('com.github.luben:zstd-jni:1.1.1'))
+// include(dependency('org.javassist:javassist:3.22.0-CR1'))
+ include(dependency('co.aikar:fastutil-lite:1.0'))
+ include(dependency(':core'))
+ include(dependency('org.yaml:snakeyaml:1.16'))
+ }
+ archiveName = "${parent.name}-${project.name}-${parent.version}.jar"
+ destinationDir = file '../target'
+ manifest {
+ attributes("Main-Class": "com.boydti.fawe.installer.InstallerFrame")
+ }
+}
+shadowJar.doLast {
+ task ->
+ ant.checksum file: task.archivePath
+}
+
+
+reobf {
+ shadowJar {
+ mappingType = 'SEARGE'
+ }
+}
+
+task deobfJar(type: Jar) {
+ from sourceSets.main.output
+ classifier = 'dev'
+}
+
+artifacts {
+ archives deobfJar
+}
+
+build.dependsOn(shadowJar)
diff --git a/forge112/src/main/java/com/boydti/fawe/forge/FaweForge.java b/forge112/src/main/java/com/boydti/fawe/forge/FaweForge.java
new file mode 100644
index 00000000..17373345
--- /dev/null
+++ b/forge112/src/main/java/com/boydti/fawe/forge/FaweForge.java
@@ -0,0 +1,179 @@
+package com.boydti.fawe.forge;
+
+
+import com.boydti.fawe.Fawe;
+import com.boydti.fawe.IFawe;
+import com.boydti.fawe.forge.v112.ForgeQueue_All;
+import com.boydti.fawe.object.FaweCommand;
+import com.boydti.fawe.object.FawePlayer;
+import com.boydti.fawe.object.FaweQueue;
+import com.boydti.fawe.regions.FaweMaskManager;
+import com.boydti.fawe.util.MainUtil;
+import com.boydti.fawe.util.TaskManager;
+import com.boydti.fawe.wrappers.WorldWrapper;
+import com.mojang.authlib.GameProfile;
+import com.sk89q.worldedit.EditSession;
+import com.sk89q.worldedit.forge.ForgeWorld;
+import com.sk89q.worldedit.world.World;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.UUID;
+import javax.management.InstanceAlreadyExistsException;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.server.MinecraftServer;
+import net.minecraftforge.fml.common.FMLCommonHandler;
+import net.minecraftforge.fml.common.ModMetadata;
+import org.apache.logging.log4j.Logger;
+
+public class FaweForge implements IFawe {
+
+ private final ForgeMain parent;
+ private final File directory;
+ private final Logger logger;
+ private final ModMetadata mod;
+
+ public FaweForge(ForgeMain plugin, Logger logger, ModMetadata mod, File directory) {
+ this.parent = plugin;
+ this.logger = logger;
+ this.directory = directory;
+ this.mod = mod;
+ try {
+ Fawe.set(this);
+ setupInjector();
+ } catch (InstanceAlreadyExistsException e) {
+ MainUtil.handleError(e);
+ }
+ }
+
+ public void setupInjector() {
+ try {
+ Fawe.setupInjector();
+ com.sk89q.worldedit.forge.ForgePlayer.inject();
+ } catch (Throwable e) {
+ Fawe.debug("Failed to inject WorldEdit classes.");
+ }
+ }
+
+ @Override
+ public void debug(String s) {
+ logger.debug(s);
+ }
+
+ @Override
+ public File getDirectory() {
+ return directory;
+ }
+
+ private HashMap commands = new HashMap<>();
+
+ public HashMap getCommands() {
+ return commands;
+ }
+
+ @Override
+ public void setupCommand(String label, FaweCommand cmd) {
+ this.commands.put(label, cmd);
+ }
+
+ @Override
+ public FawePlayer wrap(Object obj) {
+ EntityPlayerMP player = null;
+ if (obj instanceof String) {
+ MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance();
+ player = server.getPlayerList().getPlayerByUsername((String) obj);
+ } else if (obj instanceof UUID) {
+ MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance();
+ player = server.getPlayerList().getPlayerByUUID((UUID) obj);
+ } else if (obj instanceof EntityPlayerMP) {
+ player = (EntityPlayerMP) obj;
+ }
+ if (player == null) {
+ return null;
+ }
+ FawePlayer existing = Fawe.get().getCachedPlayer(player.getName());
+ return existing != null ? existing : new ForgePlayer(player);
+ }
+
+ @Override
+ public void setupVault() {
+ // Do nothing
+ }
+
+ @Override
+ public TaskManager getTaskManager() {
+ return new com.boydti.fawe.forge.ForgeTaskMan(512);
+ }
+
+ @Override
+ public String getWorldName(World world) {
+ if (world instanceof WorldWrapper) {
+ return getWorldName(((WorldWrapper) world).getParent());
+ }
+ else if (world instanceof EditSession) {
+ return getWorldName(((EditSession) world).getWorld());
+ }
+ return getWorldName(((ForgeWorld) world).getWorld());
+
+ }
+
+ public String getWorldName(net.minecraft.world.World w) {
+ return w.getWorldInfo().getWorldName() + ";" + w.provider.getDimension();
+ }
+
+ @Override
+ public FaweQueue getNewQueue(World world, boolean dontCareIfFast) {
+ return new ForgeQueue_All(world);
+ }
+
+ @Override
+ public FaweQueue getNewQueue(String world, boolean dontCareIfFast) {
+ return new ForgeQueue_All(world);
+ }
+
+ @Override
+ public Collection getMaskManagers() {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public void startMetrics() {
+ try {
+ com.boydti.fawe.forge.ForgeMetrics metrics = new com.boydti.fawe.forge.ForgeMetrics("FastAsyncWorldEdit", "3.5.1");
+ metrics.start();
+ } catch (Throwable e) {
+ debug("[FAWE] &cFailed to load up metrics.");
+ }
+ }
+
+ @Override
+ public String getPlatform() {
+ return "forge";
+ }
+
+ @Override
+ public UUID getUUID(String name) {
+ try {
+ GameProfile profile = FMLCommonHandler.instance().getMinecraftServerInstance().getPlayerProfileCache().getGameProfileForUsername(name);
+ return profile.getId();
+ } catch (Throwable e) {
+ return null;
+ }
+ }
+
+ @Override
+ public String getName(UUID uuid) {
+ try {
+ GameProfile profile = FMLCommonHandler.instance().getMinecraftServerInstance().getPlayerProfileCache().getProfileByUUID(uuid);
+ return profile.getName();
+ } catch (Throwable e) {
+ return null;
+ }
+ }
+
+ @Override
+ public Object getBlocksHubApi() {
+ return null;
+ }
+}
diff --git a/forge112/src/main/java/com/boydti/fawe/forge/ForgeCommand.java b/forge112/src/main/java/com/boydti/fawe/forge/ForgeCommand.java
new file mode 100644
index 00000000..c4cd4883
--- /dev/null
+++ b/forge112/src/main/java/com/boydti/fawe/forge/ForgeCommand.java
@@ -0,0 +1,42 @@
+package com.boydti.fawe.forge;
+
+import com.boydti.fawe.object.FaweCommand;
+import com.boydti.fawe.object.FawePlayer;
+import net.minecraft.command.CommandBase;
+import net.minecraft.command.CommandException;
+import net.minecraft.command.ICommandSender;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.server.MinecraftServer;
+
+public class ForgeCommand extends CommandBase {
+
+ private final String name;
+ private final FaweCommand cmd;
+
+ public ForgeCommand(String name, FaweCommand cmd) {
+ this.name = name;
+ this.cmd = cmd;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String getUsage(ICommandSender iCommandSender) {
+ return "/" + name;
+ }
+
+ @Override
+ public void execute(MinecraftServer minecraftServer, ICommandSender sender, String[] args) throws CommandException {
+ if ((sender instanceof EntityPlayerMP)) {
+ EntityPlayerMP player = (EntityPlayerMP) sender;
+ if (player.world.isRemote) {
+ return;
+ }
+ FawePlayer fp = FawePlayer.wrap(player);
+ cmd.executeSafe(fp, args);
+ }
+ }
+}
diff --git a/forge112/src/main/java/com/boydti/fawe/forge/ForgeMain.java b/forge112/src/main/java/com/boydti/fawe/forge/ForgeMain.java
new file mode 100644
index 00000000..a7eba924
--- /dev/null
+++ b/forge112/src/main/java/com/boydti/fawe/forge/ForgeMain.java
@@ -0,0 +1,77 @@
+package com.boydti.fawe.forge;
+
+import com.boydti.fawe.Fawe;
+import com.boydti.fawe.object.FaweCommand;
+import com.boydti.fawe.object.FawePlayer;
+import java.io.File;
+import java.util.Map;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraftforge.common.MinecraftForge;
+import net.minecraftforge.event.entity.EntityJoinWorldEvent;
+import net.minecraftforge.fml.common.FMLCommonHandler;
+import net.minecraftforge.fml.common.Mod;
+import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
+import net.minecraftforge.fml.common.event.FMLServerStartingEvent;
+import net.minecraftforge.fml.common.event.FMLServerStoppingEvent;
+import net.minecraftforge.fml.common.eventhandler.EventPriority;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+import net.minecraftforge.fml.common.gameevent.PlayerEvent;
+import org.apache.logging.log4j.Logger;
+
+@Mod(modid = "com.boydti.fawe", name = "FastAsyncWorldEdit", version = "3.5.1", acceptableRemoteVersions = "*", dependencies = "before:worldedit")
+public class ForgeMain {
+ private static com.boydti.fawe.forge.FaweForge IMP;
+ private Logger logger;
+
+ @Mod.EventHandler
+ public void preInit(FMLPreInitializationEvent event) {
+ this.logger = event.getModLog();
+ File directory = new File(event.getModConfigurationDirectory() + File.separator + "FastAsyncWorldEdit");
+ MinecraftForge.EVENT_BUS.register(this);
+ FMLCommonHandler.instance().bus().register(this);
+ this.IMP = new FaweForge(this, event.getModLog(), event.getModMetadata(), directory);
+ }
+
+ @Mod.EventHandler
+ public void serverLoad(FMLServerStartingEvent event) {
+ for (Map.Entry entry : IMP.getCommands().entrySet()) {
+ event.registerServerCommand(new ForgeCommand(entry.getKey(), entry.getValue()));
+ }
+ }
+
+ @SubscribeEvent(priority = EventPriority.LOWEST)
+ public void onPlayerQuit(PlayerEvent.PlayerLoggedOutEvent event) {
+ if (event.player.world.isRemote) {
+ return;
+ }
+ handleQuit((EntityPlayerMP) event.player);
+ }
+
+ @Mod.EventHandler
+ public void serverStopping(FMLServerStoppingEvent event) {
+ for (EntityPlayerMP player : FMLCommonHandler.instance().getMinecraftServerInstance().getPlayerList().getPlayers()) {
+ handleQuit(player);
+ }
+ }
+
+ public void handleQuit(EntityPlayerMP player) {
+ FawePlayer fp = FawePlayer.wrap(player);
+ if (fp != null) {
+ fp.unregister();
+ }
+ Fawe.get().unregister(player.getName());
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGHEST)
+ public void onPlayerChangedWorld(EntityJoinWorldEvent event) {
+ Entity entity = event.getEntity();
+ if (!(entity instanceof EntityPlayerMP)) {
+ return;
+ }
+ EntityPlayerMP player = (EntityPlayerMP) entity;
+ if (player.world.isRemote) {
+ return;
+ }
+ }
+}
diff --git a/forge112/src/main/java/com/boydti/fawe/forge/ForgeMetrics.java b/forge112/src/main/java/com/boydti/fawe/forge/ForgeMetrics.java
new file mode 100644
index 00000000..3142ebc7
--- /dev/null
+++ b/forge112/src/main/java/com/boydti/fawe/forge/ForgeMetrics.java
@@ -0,0 +1,476 @@
+/*
+ * Copyright 2011-2013 Tyler Blair. All rights reserved.
+ * Ported to Minecraft Forge by Mike Primm
+ * 1.7.x update by Dries007
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * The views and conclusions contained in the software and documentation are those of the
+ * authors and contributors and should not be interpreted as representing official policies,
+ * either expressed or implied, of anybody else.
+ */
+
+package com.boydti.fawe.forge;
+
+import com.boydti.fawe.object.io.FastByteArrayOutputStream;
+import com.boydti.fawe.object.io.PGZIPOutputStream;
+import com.boydti.fawe.util.MainUtil;
+import java.io.*;
+import java.net.Proxy;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLEncoder;
+import java.util.UUID;
+import net.minecraft.server.MinecraftServer;
+import net.minecraftforge.common.config.Configuration;
+import net.minecraftforge.fml.common.FMLCommonHandler;
+import net.minecraftforge.fml.common.FMLLog;
+import net.minecraftforge.fml.common.Loader;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+import net.minecraftforge.fml.common.gameevent.TickEvent;
+
+public class ForgeMetrics {
+
+ /**
+ * The current revision number
+ */
+ private final static int REVISION = 7;
+
+ /**
+ * The base url of the metrics domain
+ */
+ private static final String BASE_URL = "http://report.mcstats.org";
+
+ /**
+ * The url used to report a server's status
+ */
+ private static final String REPORT_URL = "/plugin/%s";
+
+ /**
+ * Interval of time to ping (in minutes)
+ */
+ private static final int PING_INTERVAL = 15;
+
+ /**
+ * The mod this metrics submits for
+ */
+ private final String modName;
+
+ private final String modVersion;
+
+ /**
+ * The metrics configuration file
+ */
+ private final Configuration configuration;
+
+ /**
+ * The metrics configuration file
+ */
+ private final File configurationFile;
+
+ /**
+ * Unique server id
+ */
+ private final String guid;
+
+ /**
+ * Debug mode
+ */
+ private final boolean debug;
+
+ private Thread thread = null;
+ private boolean firstPost = true;
+ int tickCount;
+
+ public ForgeMetrics(final String modName, final String modVersion) throws IOException {
+ if (modName == null || modVersion == null) {
+ throw new IllegalArgumentException("modName and modVersion cannot be null");
+ }
+
+ this.modName = modName;
+ this.modVersion = modVersion;
+
+ // load the config
+ configurationFile = getConfigFile();
+ configuration = new Configuration(configurationFile);
+
+ // Get values, and add some defaults, if needed
+ configuration.get(Configuration.CATEGORY_GENERAL, "opt-out", false, "Set to true to disable all reporting");
+ guid = configuration.get(Configuration.CATEGORY_GENERAL, "guid", UUID.randomUUID().toString(), "Server unique ID").getString();
+ debug = configuration.get(Configuration.CATEGORY_GENERAL, "debug", false, "Set to true for verbose debug").getBoolean(false);
+ configuration.save();
+ }
+
+ /**
+ * Start measuring statistics. This will immediately create an async
+ * repeating task as the plugin and send the initial data to the metrics
+ * backend, and then after that it will post in increments of PING_INTERVAL
+ * * 1200 ticks.
+ *
+ * @return True if statistics measuring is running, otherwise false.
+ */
+ public boolean start() {
+ // Did we opt out?
+ if (isOptOut()) {
+ return false;
+ }
+
+ FMLCommonHandler.instance().bus().register(this);
+
+ return true;
+ }
+
+ @SubscribeEvent
+ public void tick(TickEvent.ServerTickEvent tick) {
+ if (tick.phase != TickEvent.Phase.END) return;
+
+ if (tickCount++ % (PING_INTERVAL * 1200) != 0) return;
+
+ if (thread == null) {
+ thread = new Thread(new Runnable() {
+ public void run() {
+ try {
+ // Disable Task, if it is running and the server owner decided
+ // to opt-out
+ if (isOptOut()) {
+ FMLCommonHandler.instance().bus().unregister(ForgeMetrics.this);
+ return;
+ }
+ // We use the inverse of firstPost because if it
+ // is the first time we are posting,
+ // it is not a interval ping, so it evaluates to
+ // FALSE
+ // Each time thereafter it will evaluate to
+ // TRUE, i.e PING!
+ postPlugin(!firstPost);
+ // After the first post we set firstPost to
+ // false
+ // Each post thereafter will be a ping
+ firstPost = false;
+ } catch (IOException e) {
+ if (debug) {
+ FMLLog.info("[Metrics] Exception - %s", e.getMessage());
+ }
+ } finally {
+ thread = null;
+ }
+ }
+ });
+ thread.start();
+ }
+ }
+
+ /**
+ * Stop processing
+ */
+ public void stop() {
+ }
+
+ /**
+ * Has the server owner denied plugin metrics?
+ *
+ * @return true if metrics should be opted out of it
+ */
+ public boolean isOptOut() {
+ // Reload the metrics file
+ configuration.load();
+ return configuration.get(Configuration.CATEGORY_GENERAL, "opt-out", false).getBoolean(false);
+ }
+
+ /**
+ * Enables metrics for the server by setting "opt-out" to false in the
+ * config file and starting the metrics task.
+ *
+ * @throws java.io.IOException
+ */
+ public void enable() throws IOException {
+ // Check if the server owner has already set opt-out, if not, set it.
+ if (isOptOut()) {
+ configuration.getCategory(Configuration.CATEGORY_GENERAL).get("opt-out").set("false");
+ configuration.save();
+ }
+ // Enable Task, if it is not running
+ FMLCommonHandler.instance().bus().register(this);
+ }
+
+ /**
+ * Disables metrics for the server by setting "opt-out" to true in the
+ * config file and canceling the metrics task.
+ *
+ * @throws java.io.IOException
+ */
+ public void disable() throws IOException {
+ // Check if the server owner has already set opt-out, if not, set it.
+ if (!isOptOut()) {
+ configuration.getCategory(Configuration.CATEGORY_GENERAL).get("opt-out").set("true");
+ configuration.save();
+ }
+ FMLCommonHandler.instance().bus().unregister(this);
+ }
+
+ /**
+ * Gets the File object of the config file that should be used to store data
+ * such as the GUID and opt-out status
+ *
+ * @return the File object for the config file
+ */
+ public File getConfigFile() {
+ return new File(Loader.instance().getConfigDir(), "PluginMetrics.cfg");
+ }
+
+ /**
+ * Generic method that posts a plugin to the metrics website
+ */
+ private void postPlugin(final boolean isPing) throws IOException {
+ // Server software specific section
+ String pluginName = modName;
+ MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance();
+ boolean onlineMode = server.isServerInOnlineMode();
+ String pluginVersion = modVersion;
+ String serverVersion;
+ if (server.isDedicatedServer()) {
+ serverVersion = "MinecraftForge (MC: " + server.getMinecraftVersion() + ")";
+ } else {
+ serverVersion = "MinecraftForgeSSP (MC: " + server.getMinecraftVersion() + ")";
+ }
+ int playersOnline = server.getCurrentPlayerCount();
+
+ // END server software specific section -- all code below does not use any code outside of this class / Java
+
+ // Construct the post data
+ StringBuilder json = new StringBuilder(1024);
+ json.append('{');
+
+ // The plugin's description file containg all of the plugin data such as name, version, author, etc
+ appendJSONPair(json, "guid", guid);
+ appendJSONPair(json, "plugin_version", pluginVersion);
+ appendJSONPair(json, "server_version", serverVersion);
+ appendJSONPair(json, "players_online", Integer.toString(playersOnline));
+
+ // New data as of R6
+ String osname = System.getProperty("os.name");
+ String osarch = System.getProperty("os.arch");
+ String osversion = System.getProperty("os.version");
+ String java_version = System.getProperty("java.version");
+ int coreCount = Runtime.getRuntime().availableProcessors();
+
+ // normalize os arch .. amd64 -> x86_64
+ if (osarch.equals("amd64")) {
+ osarch = "x86_64";
+ }
+
+ appendJSONPair(json, "osname", osname);
+ appendJSONPair(json, "osarch", osarch);
+ appendJSONPair(json, "osversion", osversion);
+ appendJSONPair(json, "cores", Integer.toString(coreCount));
+ appendJSONPair(json, "auth_mode", onlineMode ? "1" : "0");
+ appendJSONPair(json, "java_version", java_version);
+
+ // If we're pinging, append it
+ if (isPing) {
+ appendJSONPair(json, "ping", "1");
+ }
+
+ // close json
+ json.append('}');
+
+ // Create the url
+ URL url = new URL(BASE_URL + String.format(REPORT_URL, urlEncode(pluginName)));
+
+ // Connect to the website
+ URLConnection connection;
+
+ // Mineshafter creates a socks proxy, so we can safely bypass it
+ // It does not reroute POST requests so we need to go around it
+ if (isMineshafterPresent()) {
+ connection = url.openConnection(Proxy.NO_PROXY);
+ } else {
+ connection = url.openConnection();
+ }
+
+
+ byte[] uncompressed = json.toString().getBytes();
+ byte[] compressed = gzip(json.toString());
+
+ // Headers
+ connection.addRequestProperty("User-Agent", "MCStats/" + REVISION);
+ connection.addRequestProperty("Content-Type", "application/json");
+ connection.addRequestProperty("Content-Encoding", "gzip");
+ connection.addRequestProperty("Content-Length", Integer.toString(compressed.length));
+ connection.addRequestProperty("Accept", "application/json");
+ connection.addRequestProperty("Connection", "close");
+
+ connection.setDoOutput(true);
+
+ // Write the data
+ OutputStream os = connection.getOutputStream();
+ os.write(compressed);
+ os.flush();
+
+ // Now read the response
+ final BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
+ String response = reader.readLine();
+
+ // close resources
+ os.close();
+ reader.close();
+
+ if (response == null || response.startsWith("ERR") || response.startsWith("7")) {
+ if (response == null) {
+ response = "null";
+ } else if (response.startsWith("7")) {
+ response = response.substring(response.startsWith("7,") ? 2 : 1);
+ }
+
+ throw new IOException(response);
+ }
+ }
+
+ /**
+ * GZip compress a string of bytes
+ *
+ * @param input
+ * @return
+ */
+ public static byte[] gzip(String input) {
+ FastByteArrayOutputStream baos = new FastByteArrayOutputStream();
+ PGZIPOutputStream gzos = null;
+
+ try {
+ gzos = new PGZIPOutputStream(baos);
+ gzos.write(input.getBytes("UTF-8"));
+ } catch (IOException e) {
+ MainUtil.handleError(e);
+ } finally {
+ if (gzos != null) try {
+ gzos.close();
+ } catch (IOException ignore) {
+ }
+ }
+
+ return baos.toByteArray();
+ }
+
+ /**
+ * Check if mineshafter is present. If it is, we need to bypass it to send POST requests
+ *
+ * @return true if mineshafter is installed on the server
+ */
+ private boolean isMineshafterPresent() {
+ try {
+ Class.forName("mineshafter.MineServer");
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ /**
+ * Appends a json encoded key/value pair to the given string builder.
+ *
+ * @param json
+ * @param key
+ * @param value
+ * @throws java.io.UnsupportedEncodingException
+ */
+ private static void appendJSONPair(StringBuilder json, String key, String value) throws UnsupportedEncodingException {
+ boolean isValueNumeric = false;
+
+ try {
+ if (value.equals("0") || !value.endsWith("0")) {
+ Double.parseDouble(value);
+ isValueNumeric = true;
+ }
+ } catch (NumberFormatException e) {
+ isValueNumeric = false;
+ }
+
+ if (json.charAt(json.length() - 1) != '{') {
+ json.append(',');
+ }
+
+ json.append(escapeJSON(key));
+ json.append(':');
+
+ if (isValueNumeric) {
+ json.append(value);
+ } else {
+ json.append(escapeJSON(value));
+ }
+ }
+
+ /**
+ * Escape a string to create a valid JSON string
+ *
+ * @param text
+ * @return
+ */
+ private static String escapeJSON(String text) {
+ StringBuilder builder = new StringBuilder();
+
+ builder.append('"');
+ for (int index = 0; index < text.length(); index++) {
+ char chr = text.charAt(index);
+
+ switch (chr) {
+ case '"':
+ case '\\':
+ builder.append('\\');
+ builder.append(chr);
+ break;
+ case '\b':
+ builder.append("\\b");
+ break;
+ case '\t':
+ builder.append("\\t");
+ break;
+ case '\n':
+ builder.append("\\n");
+ break;
+ case '\r':
+ builder.append("\\r");
+ break;
+ default:
+ if (chr < ' ') {
+ String t = "000" + Integer.toHexString(chr);
+ builder.append("\\u" + t.substring(t.length() - 4));
+ } else {
+ builder.append(chr);
+ }
+ break;
+ }
+ }
+ builder.append('"');
+
+ return builder.toString();
+ }
+
+ /**
+ * Encode text as UTF-8
+ *
+ * @param text the text to encode
+ * @return the encoded text, as UTF-8
+ */
+ private static String urlEncode(final String text) throws UnsupportedEncodingException {
+ return URLEncoder.encode(text, "UTF-8");
+ }
+
+}
\ No newline at end of file
diff --git a/forge112/src/main/java/com/boydti/fawe/forge/ForgePlayer.java b/forge112/src/main/java/com/boydti/fawe/forge/ForgePlayer.java
new file mode 100644
index 00000000..ed2461a6
--- /dev/null
+++ b/forge112/src/main/java/com/boydti/fawe/forge/ForgePlayer.java
@@ -0,0 +1,77 @@
+package com.boydti.fawe.forge;
+
+import com.boydti.fawe.Fawe;
+import com.boydti.fawe.config.BBC;
+import com.boydti.fawe.config.Settings;
+import com.boydti.fawe.object.FaweLocation;
+import com.boydti.fawe.object.FawePlayer;
+import com.boydti.fawe.wrappers.PlayerWrapper;
+import com.sk89q.worldedit.entity.Player;
+import com.sk89q.worldedit.forge.ForgeWorldEdit;
+import java.util.UUID;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.text.TextComponentString;
+import net.minecraft.world.World;
+
+public class ForgePlayer extends FawePlayer {
+ public ForgePlayer(EntityPlayerMP parent) {
+ super(parent);
+ }
+
+ @Override
+ public void sendTitle(String head, String sub) { // Not supported
+ Settings.IMP.QUEUE.PROGRESS.DISPLAY = "false";
+ }
+
+ @Override
+ public void resetTitle() { // Not supported
+ Settings.IMP.QUEUE.PROGRESS.DISPLAY = "false";
+ }
+
+ @Override
+ public String getName() {
+ return parent.getName();
+ }
+
+ @Override
+ public UUID getUUID() {
+ return parent.getUniqueID();
+ }
+
+ @Override
+ public boolean hasPermission(String perm) {
+ Object meta = getMeta(perm);
+ return meta instanceof Boolean ? (boolean) meta : ForgeWorldEdit.inst.getPermissionsProvider().hasPermission(parent, perm);
+ }
+
+ @Override
+ public void setPermission(String perm, boolean flag) {
+ setMeta(perm, flag);
+ }
+
+ @Override
+ public void sendMessage(String msg) {
+ msg = BBC.color(msg);
+ for (String line : msg.split("\n")) {
+ this.parent.sendMessage(new TextComponentString(line));
+ }
+ }
+
+ @Override
+ public void executeCommand(String substring) {
+ throw new UnsupportedOperationException("NOT IMPLEMENTED");
+ }
+
+ @Override
+ public FaweLocation getLocation() {
+ World world = parent.world;
+ BlockPos pos = parent.getPosition();
+ return new FaweLocation(Fawe.imp().getWorldName(world), pos.getX(), pos.getY(), pos.getZ());
+ }
+
+ @Override
+ public Player toWorldEditPlayer() {
+ return PlayerWrapper.wrap(ForgeWorldEdit.inst.wrap(this.parent));
+ }
+}
diff --git a/forge112/src/main/java/com/boydti/fawe/forge/ForgePlayerBlockBag.java b/forge112/src/main/java/com/boydti/fawe/forge/ForgePlayerBlockBag.java
new file mode 100644
index 00000000..183ee439
--- /dev/null
+++ b/forge112/src/main/java/com/boydti/fawe/forge/ForgePlayerBlockBag.java
@@ -0,0 +1,199 @@
+package com.boydti.fawe.forge;
+
+import com.sk89q.worldedit.WorldVector;
+import com.sk89q.worldedit.blocks.BaseItem;
+import com.sk89q.worldedit.blocks.BaseItemStack;
+import com.sk89q.worldedit.blocks.BlockID;
+import com.sk89q.worldedit.blocks.ItemType;
+import com.sk89q.worldedit.extent.inventory.BlockBag;
+import com.sk89q.worldedit.extent.inventory.BlockBagException;
+import com.sk89q.worldedit.extent.inventory.OutOfBlocksException;
+import com.sk89q.worldedit.extent.inventory.OutOfSpaceException;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+
+public class ForgePlayerBlockBag extends BlockBag {
+
+ private EntityPlayerMP player;
+ private ItemStack[] items;
+ private boolean changed;
+
+ /**
+ * Construct the object.
+ *
+ * @param player the player
+ */
+ public ForgePlayerBlockBag(EntityPlayerMP player) {
+ this.player = player;
+ }
+
+ /**
+ * Loads inventory on first use.
+ */
+ private void loadInventory() {
+ if (items == null) {
+ items = new ItemStack[player.inventory.getSizeInventory()];
+ for (int i = 0; i < player.inventory.getSizeInventory(); i++) {
+ items[i] = player.inventory.getStackInSlot(i);
+ }
+ }
+ }
+
+ /**
+ * Get the player.
+ *
+ * @return the player
+ */
+ public EntityPlayerMP getPlayer() {
+ return player;
+ }
+
+ @Override
+ public void fetchItem(BaseItem item) throws BlockBagException {
+ final int id = item.getType();
+ final int damage = item.getData();
+ int amount = (item instanceof BaseItemStack) ? ((BaseItemStack) item).getAmount() : 1;
+ assert(amount == 1);
+ boolean usesDamageValue = ItemType.usesDamageValue(id);
+
+ if (id == BlockID.AIR) {
+ throw new IllegalArgumentException("Can't fetch air block");
+ }
+
+ loadInventory();
+
+ boolean found = false;
+
+ for (int slot = 0; slot < items.length; ++slot) {
+ ItemStack forgeItem = items[slot];
+
+ if (forgeItem == null) {
+ continue;
+ }
+ int itemId = Item.getIdFromItem(forgeItem.getItem());
+ if (itemId != id) {
+ // Type id doesn't fit
+ continue;
+ }
+
+ if (usesDamageValue && forgeItem.getItemDamage() != damage) {
+ // Damage value doesn't fit.
+ continue;
+ }
+
+ int currentAmount = forgeItem.getCount();
+ if (currentAmount < 0) {
+ // Unlimited
+ return;
+ }
+
+ changed = true;
+
+ if (currentAmount > 1) {
+ forgeItem.setCount(forgeItem.getCount() - 1);;
+ found = true;
+ } else {
+ items[slot] = null;
+ found = true;
+ }
+
+ break;
+ }
+
+ if (!found) {
+ throw new OutOfBlocksException();
+ }
+ }
+
+ @Override
+ public void storeItem(BaseItem item) throws BlockBagException {
+ final int id = item.getType();
+ final int damage = item.getData();
+ int amount = (item instanceof BaseItemStack) ? ((BaseItemStack) item).getAmount() : 1;
+ assert(amount <= 64);
+ boolean usesDamageValue = ItemType.usesDamageValue(id);
+
+ if (id == BlockID.AIR) {
+ throw new IllegalArgumentException("Can't store air block");
+ }
+
+ loadInventory();
+
+ int freeSlot = -1;
+
+ for (int slot = 0; slot < items.length; ++slot) {
+ ItemStack forgeItem = items[slot];
+
+ if (forgeItem == null) {
+ // Delay using up a free slot until we know there are no stacks
+ // of this item to merge into
+
+ if (freeSlot == -1) {
+ freeSlot = slot;
+ }
+ continue;
+ }
+
+ int itemId = Item.getIdFromItem(forgeItem.getItem());
+ if (itemId != id) {
+ // Type id doesn't fit
+ continue;
+ }
+
+ if (usesDamageValue && forgeItem.getItemDamage() != damage) {
+ // Damage value doesn't fit.
+ continue;
+ }
+
+ int currentAmount = forgeItem.getCount();
+ if (currentAmount < 0) {
+ // Unlimited
+ return;
+ }
+ if (currentAmount >= 64) {
+ // Full stack
+ continue;
+ }
+
+ changed = true;
+
+ int spaceLeft = 64 - currentAmount;
+ if (spaceLeft >= amount) {
+ forgeItem.setCount(forgeItem.getCount() + amount);
+ return;
+ }
+
+ forgeItem.setCount(64);
+ amount -= spaceLeft;
+ }
+
+ if (freeSlot > -1) {
+ changed = true;
+ items[freeSlot] = new ItemStack(Item.getItemById(id), amount);
+ return;
+ }
+
+ throw new OutOfSpaceException(id);
+ }
+
+ @Override
+ public void flushChanges() {
+ if (items != null && changed) {
+ for (int i = 0; i < items.length; i++) {
+ player.inventory.setInventorySlotContents(i, items[i]);
+ }
+ items = null;
+ changed = false;
+ }
+ }
+
+ @Override
+ public void addSourcePosition(WorldVector pos) {
+ }
+
+ @Override
+ public void addSingleSourcePosition(WorldVector pos) {
+ }
+
+}
\ No newline at end of file
diff --git a/forge112/src/main/java/com/boydti/fawe/forge/ForgeTaskMan.java b/forge112/src/main/java/com/boydti/fawe/forge/ForgeTaskMan.java
new file mode 100644
index 00000000..39eb1155
--- /dev/null
+++ b/forge112/src/main/java/com/boydti/fawe/forge/ForgeTaskMan.java
@@ -0,0 +1,168 @@
+package com.boydti.fawe.forge;
+
+import com.boydti.fawe.Fawe;
+import com.boydti.fawe.util.MainUtil;
+import com.boydti.fawe.util.TaskManager;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedDeque;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+import net.minecraftforge.fml.common.FMLCommonHandler;
+import net.minecraftforge.fml.common.eventhandler.EventPriority;
+import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
+import net.minecraftforge.fml.common.gameevent.TickEvent;
+
+public class ForgeTaskMan extends TaskManager {
+
+ private final ConcurrentLinkedDeque syncTasks = new ConcurrentLinkedDeque<>();
+ private final ConcurrentLinkedDeque asyncTasks = new ConcurrentLinkedDeque<>();
+
+ private final ConcurrentHashMap taskIdMap = new ConcurrentHashMap<>(8, 0.9f, 1);
+
+
+ private final AtomicInteger taskId = new AtomicInteger();
+ private final ExecutorService executor;
+
+ public ForgeTaskMan(int size) {
+ this.executor = Executors.newFixedThreadPool(size);
+ FMLCommonHandler.instance().bus().register(this);
+ }
+
+
+ @Override
+ public int repeat(final Runnable r, final int interval) {
+ if (r == null) {
+ return -1;
+ }
+ int id = taskId.incrementAndGet();
+ taskIdMap.put(id, r);
+ task(new Runnable() {
+ @Override
+ public void run() {
+ if (!taskIdMap.containsKey(id)) {
+ return;
+ }
+ try {
+ r.run();
+ } catch (Throwable e) {
+ MainUtil.handleError(e);
+ }
+ later(this, interval);
+ }
+ });
+ return id;
+ }
+
+ @SubscribeEvent(priority = EventPriority.HIGHEST)
+ public void onServerTick(TickEvent.ServerTickEvent event) {
+ Fawe.get().setMainThread();
+ int asyncSize = asyncTasks.size();
+ for (int i = 0; i < asyncSize; i++) {
+ Runnable item = asyncTasks.poll();
+ if (item != null) {
+ async(item);
+ }
+ }
+ int syncSize = syncTasks.size();
+ for (int i = 0; i < syncSize; i++) {
+ Runnable item = syncTasks.poll();
+ if (item != null) {
+ try {
+ item.run();
+ } catch (Throwable e) {
+ MainUtil.handleError(e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public int repeatAsync(Runnable r, int interval) {
+ if (r == null) {
+ return -1;
+ }
+ int id = taskId.incrementAndGet();
+ taskIdMap.put(id, r);
+ async(new Runnable() {
+ @Override
+ public void run() {
+ if (!taskIdMap.containsKey(id)) {
+ return;
+ }
+ try {
+ r.run();
+ } catch (Throwable e) {
+ MainUtil.handleError(e);
+ }
+ laterAsync(this, interval);
+ }
+ });
+ return id;
+ }
+
+ @Override
+ public void async(Runnable r) {
+ if (r == null) {
+ return;
+ }
+ executor.execute(r);
+ }
+
+ @Override
+ public void task(Runnable r) {
+ if (r == null) {
+ return;
+ }
+ syncTasks.add(r);
+ }
+
+ @Override
+ public void later(Runnable r, int delay) {
+ if (r == null) {
+ return;
+ }
+ AtomicInteger remaining = new AtomicInteger(delay);
+ task(new Runnable() {
+ @Override
+ public void run() {
+ if (remaining.decrementAndGet() <= 0) {
+ try {
+ r.run();
+ } catch (Throwable e) {
+ MainUtil.handleError(e);
+ }
+ return;
+ }
+ task(this);
+ }
+ });
+ }
+
+ @Override
+ public void laterAsync(Runnable r, int delay) {
+ if (r == null) {
+ return;
+ }
+ AtomicInteger remaining = new AtomicInteger(delay);
+ task(new Runnable() {
+ @Override
+ public void run() {
+ if (remaining.decrementAndGet() <= 0) {
+ try {
+ async(r);
+ } catch (Throwable e) {
+ MainUtil.handleError(e);
+ }
+ return;
+ }
+ task(this);
+ }
+ });
+ }
+
+ @Override
+ public void cancel(int task) {
+ taskIdMap.remove(task);
+ }
+}
diff --git a/forge112/src/main/java/com/boydti/fawe/forge/MutableGenLayer.java b/forge112/src/main/java/com/boydti/fawe/forge/MutableGenLayer.java
new file mode 100644
index 00000000..70b06c13
--- /dev/null
+++ b/forge112/src/main/java/com/boydti/fawe/forge/MutableGenLayer.java
@@ -0,0 +1,26 @@
+package com.boydti.fawe.forge;
+
+import java.util.Arrays;
+import net.minecraft.world.gen.layer.GenLayer;
+import net.minecraft.world.gen.layer.IntCache;
+
+public class MutableGenLayer extends GenLayer {
+
+ private int biome;
+
+ public MutableGenLayer(long seed) {
+ super(seed);
+ }
+
+ public MutableGenLayer set(int biome) {
+ this.biome = biome;
+ return this;
+ }
+
+ @Override
+ public int[] getInts(int areaX, int areaY, int areaWidth, int areaHeight) {
+ int[] biomes = IntCache.getIntCache(areaWidth * areaHeight);
+ Arrays.fill(biomes, biome);
+ return biomes;
+ }
+}
diff --git a/forge112/src/main/java/com/boydti/fawe/forge/v112/ForgeChunk_All.java b/forge112/src/main/java/com/boydti/fawe/forge/v112/ForgeChunk_All.java
new file mode 100644
index 00000000..258d3e41
--- /dev/null
+++ b/forge112/src/main/java/com/boydti/fawe/forge/v112/ForgeChunk_All.java
@@ -0,0 +1,388 @@
+package com.boydti.fawe.forge.v112;
+
+import com.boydti.fawe.Fawe;
+import com.boydti.fawe.FaweCache;
+import com.boydti.fawe.example.CharFaweChunk;
+import com.boydti.fawe.object.FaweQueue;
+import com.boydti.fawe.util.MainUtil;
+import com.sk89q.jnbt.CompoundTag;
+import com.sk89q.jnbt.ListTag;
+import com.sk89q.jnbt.StringTag;
+import com.sk89q.jnbt.Tag;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collection;
+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 net.minecraft.block.Block;
+import net.minecraft.block.state.IBlockState;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.EntityList;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.BitArray;
+import net.minecraft.util.ClassInheritanceMultiMap;
+import net.minecraft.util.ResourceLocation;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.world.World;
+import net.minecraft.world.chunk.BlockStateContainer;
+import net.minecraft.world.chunk.BlockStatePaletteRegistry;
+import net.minecraft.world.chunk.Chunk;
+import net.minecraft.world.chunk.IBlockStatePalette;
+import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
+
+public class ForgeChunk_All extends CharFaweChunk {
+
+ public BlockStateContainer[] sectionPalettes;
+
+ public static Map entityKeys;
+
+ /**
+ * A FaweSections object represents a chunk and the blocks that you wish to change in it.
+ *
+ * @param parent
+ * @param x
+ * @param z
+ */
+ public ForgeChunk_All(FaweQueue parent, int x, int z) {
+ super(parent, x, z);
+ }
+
+ public ForgeChunk_All(FaweQueue parent, int x, int z, char[][] ids, short[] count, short[] air, byte[] heightMap) {
+ super(parent, x, z, ids, count, air, heightMap);
+ }
+
+ @Override
+ public CharFaweChunk copy(boolean shallow) {
+ ForgeChunk_All copy;
+ if (shallow) {
+ copy = new ForgeChunk_All(getParent(), getX(), getZ(), ids, count, air, heightMap);
+ copy.biomes = biomes;
+ copy.chunk = chunk;
+ } else {
+ copy = new ForgeChunk_All(getParent(), getX(), getZ(), (char[][]) MainUtil.copyNd(ids), count.clone(), air.clone(), heightMap.clone());
+ copy.biomes = biomes;
+ copy.chunk = chunk;
+ copy.biomes = biomes.clone();
+ copy.chunk = chunk;
+ }
+ if (sectionPalettes != null) {
+ copy.sectionPalettes = new BlockStateContainer[16];
+ try {
+ Field fieldBits = BlockStateContainer.class.getDeclaredField("field_186021_b"); // storage
+ fieldBits.setAccessible(true);
+ Field fieldPalette = BlockStateContainer.class.getDeclaredField("field_186022_c"); // palettes
+ fieldPalette.setAccessible(true);
+ Field fieldSize = BlockStateContainer.class.getDeclaredField("field_186024_e"); // bits
+ fieldSize.setAccessible(true);
+ for (int i = 0; i < sectionPalettes.length; i++) {
+ BlockStateContainer current = sectionPalettes[i];
+ if (current == null) {
+ continue;
+ }
+ // Clone palette
+ IBlockStatePalette currentPalette = (IBlockStatePalette) fieldPalette.get(current);
+ if (!(currentPalette instanceof BlockStatePaletteRegistry)) {
+ current.onResize(128, null);
+ }
+ BlockStateContainer paletteBlock = new BlockStateContainer();
+ currentPalette = (IBlockStatePalette) fieldPalette.get(current);
+ if (!(currentPalette instanceof BlockStatePaletteRegistry)) {
+ throw new RuntimeException("Palette must be global!");
+ }
+ fieldPalette.set(paletteBlock, currentPalette);
+ // Clone size
+ fieldSize.set(paletteBlock, fieldSize.get(current));
+ // Clone palette
+ BitArray currentBits = (BitArray) fieldBits.get(current);
+ BitArray newBits = new BitArray(1, 0);
+ for (Field field : BitArray.class.getDeclaredFields()) {
+ field.setAccessible(true);
+ Object currentValue = field.get(currentBits);
+ if (currentValue instanceof long[]) {
+ currentValue = ((long[]) currentValue).clone();
+ }
+ field.set(newBits, currentValue);
+ }
+ fieldBits.set(paletteBlock, newBits);
+ copy.sectionPalettes[i] = paletteBlock;
+ }
+ } catch (Throwable e) {
+ MainUtil.handleError(e);
+ }
+ }
+ return copy;
+ }
+
+ @Override
+ public Chunk getNewChunk() {
+ World world = ((ForgeQueue_All) getParent()).getWorld();
+ return world.getChunkProvider().provideChunk(getX(), getZ());
+ }
+
+ public void optimize() {
+ if (sectionPalettes != null) {
+ return;
+ }
+ char[][] arrays = getCombinedIdArrays();
+ char lastChar = Character.MAX_VALUE;
+ for (int layer = 0; layer < 16; layer++) {
+ if (getCount(layer) > 0) {
+ if (sectionPalettes == null) {
+ sectionPalettes = new BlockStateContainer[16];
+ }
+ BlockStateContainer palette = new BlockStateContainer();
+ char[] blocks = getIdArray(layer);
+ for (int y = 0; y < 16; y++) {
+ for (int z = 0; z < 16; z++) {
+ for (int x = 0; x < 16; x++) {
+ char combinedId = blocks[FaweCache.CACHE_J[y][z][x]];
+ if (combinedId > 1) {
+ palette.set(x, y, z, Block.getBlockById(combinedId >> 4).getStateFromMeta(combinedId & 0xF));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public ForgeChunk_All call() {
+ net.minecraft.world.chunk.Chunk nmsChunk = this.getChunk();
+ int bx = this.getX() << 4;
+ int bz = this.getZ() << 4;
+ nmsChunk.setModified(true);
+ net.minecraft.world.World nmsWorld = getParent().getWorld();
+ try {
+ boolean flag = nmsWorld.provider.hasSkyLight();
+ // Sections
+ ExtendedBlockStorage[] sections = nmsChunk.getBlockStorageArray();
+ Map tiles = nmsChunk.getTileEntityMap();
+ ClassInheritanceMultiMap[] entities = nmsChunk.getEntityLists();
+
+ // Set heightmap
+ getParent().setHeightMap(this, heightMap);
+ // Remove entities
+ for (int i = 0; i < 16; i++) {
+ int count = this.getCount(i);
+ if (count == 0) {
+ continue;
+ } else if (count >= 4096) {
+ ClassInheritanceMultiMap ents = entities[i];
+ if (ents != null && !ents.isEmpty()) {
+ entities[i] = new ClassInheritanceMultiMap<>(Entity.class);
+ for (Entity ent : ents) {
+ nmsWorld.removeEntity(ent);
+ }
+ }
+ } else {
+ char[] array = this.getIdArray(i);
+ if (array == null || entities[i] == null || entities[i].isEmpty()) continue;
+ Collection ents = new ArrayList<>(entities[i]);
+ for (Entity entity : ents) {
+ if (entity instanceof EntityPlayer) {
+ continue;
+ }
+ int x = ((int) Math.round(entity.posX) & 15);
+ int z = ((int) Math.round(entity.posZ) & 15);
+ int y = (int) Math.round(entity.posY);
+ if (y < 0 || y > 255) continue;
+ if (array[FaweCache.CACHE_J[y][z][x]] != 0) {
+ nmsWorld.removeEntity(entity);
+ }
+ }
+ }
+ }
+ HashSet entsToRemove = this.getEntityRemoves();
+ if (!entsToRemove.isEmpty()) {
+ for (int i = 0; i < entities.length; i++) {
+ Collection ents = new ArrayList<>(entities[i]);
+ for (Entity entity : ents) {
+ if (entsToRemove.contains(entity.getUniqueID())) {
+ nmsWorld.removeEntity(entity);
+ }
+ }
+ }
+ }
+ // Set entities
+ Set createdEntities = new HashSet<>();
+ Set entitiesToSpawn = this.getEntities();
+ for (CompoundTag nativeTag : entitiesToSpawn) {
+ Map entityTagMap = nativeTag.getValue();
+ StringTag idTag = (StringTag) entityTagMap.get("Id");
+ ListTag posTag = (ListTag) entityTagMap.get("Pos");
+ ListTag rotTag = (ListTag) entityTagMap.get("Rotation");
+ if (idTag == null || posTag == null || rotTag == null) {
+ Fawe.debug("Unknown entity tag: " + nativeTag);
+ continue;
+ }
+ double x = posTag.getDouble(0);
+ double y = posTag.getDouble(1);
+ double z = posTag.getDouble(2);
+ float yaw = rotTag.getFloat(0);
+ float pitch = rotTag.getFloat(1);
+ String id = idTag.getValue();
+ if (entityKeys == null) {
+ entityKeys = new HashMap<>();
+ for (ResourceLocation key : EntityList.getEntityNameList()) {
+ String currentId = EntityList.getTranslationName(key);
+ entityKeys.put(currentId, key);
+ entityKeys.put(key.getResourcePath(), key);
+ }
+ }
+ ResourceLocation entityKey = entityKeys.get(id);
+ if (entityKey != null) {
+ Entity entity = EntityList.createEntityByIDFromName(entityKey, nmsWorld);
+ if (entity != null) {
+ NBTTagCompound tag = (NBTTagCompound)ForgeQueue_All.methodFromNative.invoke(null, nativeTag);
+ entity.readFromNBT(tag);
+ tag.removeTag("UUIDMost");
+ tag.removeTag("UUIDLeast");
+ entity.setPositionAndRotation(x, y, z, yaw, pitch);
+ nmsWorld.spawnEntity(entity);
+ }
+ }
+
+ }
+ // Run change task if applicable
+ if (getParent().getChangeTask() != null) {
+ CharFaweChunk previous = getParent().getPrevious(this, sections, tiles, entities, createdEntities, false);
+ getParent().getChangeTask().run(previous, this);
+ }
+ // Trim tiles
+ if (!tiles.isEmpty()) {
+ Set> entryset = tiles.entrySet();
+ Iterator> iterator = entryset.iterator();
+ while (iterator.hasNext()) {
+ Map.Entry tile = iterator.next();
+ BlockPos pos = tile.getKey();
+ int lx = pos.getX() & 15;
+ int ly = pos.getY();
+ int lz = pos.getZ() & 15;
+ int j = FaweCache.CACHE_I[ly][lz][lx];
+ char[] array = this.getIdArray(j);
+ if (array == null) {
+ continue;
+ }
+ int k = FaweCache.CACHE_J[ly][lz][lx];
+ if (array[k] != 0) {
+ synchronized (ForgeChunk_All.class) {
+ tile.getValue().invalidate();
+ iterator.remove();
+ }
+ }
+ }
+ }
+ // Efficiently merge sections
+ for (int j = 0; j < sections.length; j++) {
+ int count = this.getCount(j);
+ if (count == 0) {
+ continue;
+ }
+ final char[] array = this.getIdArray(j);
+ if (array == null) {
+ continue;
+ }
+ int countAir = this.getAir(j);
+ ExtendedBlockStorage section = sections[j];
+ if (section == null) {
+ if (count == countAir) {
+ continue;
+ }
+ if (this.sectionPalettes != null && this.sectionPalettes[j] != null) {
+ section = sections[j] = new ExtendedBlockStorage(j << 4, flag);
+ getParent().setPalette(section, this.sectionPalettes[j]);
+ getParent().setCount(0, count - this.getAir(j), section);
+ continue;
+ } else {
+ sections[j] = section = new ExtendedBlockStorage(j << 4, flag);
+ }
+ } else if (count >= 4096) {
+ if (count == countAir) {
+ sections[j] = null;
+ continue;
+ }
+ if (this.sectionPalettes != null && this.sectionPalettes[j] != null) {
+ getParent().setPalette(section, this.sectionPalettes[j]);
+ getParent().setCount(0, count - this.getAir(j), section);
+ continue;
+ }
+ }
+ IBlockState existing;
+ int by = j << 4;
+ BlockStateContainer nibble = section.getData();
+ int nonEmptyBlockCount = 0;
+ for (int y = 0; y < 16; y++) {
+ for (int z = 0; z < 16; z++) {
+ for (int x = 0; x < 16; x++) {
+ char combinedId = array[FaweCache.CACHE_J[y][z][x]];
+ switch (combinedId) {
+ case 0:
+ continue;
+ case 1:
+ existing = nibble.get(x, y, z);
+ if (existing != ForgeQueue_All.air) {
+ if (existing.getLightValue() > 0) {
+ getParent().getRelighter().addLightUpdate(bx + x, by + y, bz + z);
+ }
+ nonEmptyBlockCount--;
+ }
+ nibble.set(x, y, z, ForgeQueue_All.air);
+ continue;
+ default:
+ existing = nibble.get(x, y, z);
+ if (existing != ForgeQueue_All.air) {
+ if (existing.getLightValue() > 0) {
+ getParent().getRelighter().addLightUpdate(bx + x, by + y, bz + z);
+ }
+ } else {
+ nonEmptyBlockCount++;
+ }
+ nibble.set(x, y, z, Block.getBlockById(combinedId >> 4).getStateFromMeta(combinedId & 0xF));
+ }
+ }
+ }
+ }
+ getParent().setCount(0, getParent().getNonEmptyBlockCount(section) + nonEmptyBlockCount, section);
+ }
+ // Set biomes
+ if (this.biomes != null) {
+ byte[] currentBiomes = nmsChunk.getBiomeArray();
+ for (int i = 0 ; i < this.biomes.length; i++) {
+ if (this.biomes[i] != 0) {
+ currentBiomes[i] = this.biomes[i];
+ }
+ }
+ }
+ // Set tiles
+ Map tilesToSpawn = this.getTiles();
+
+ for (Map.Entry entry : tilesToSpawn.entrySet()) {
+ CompoundTag nativeTag = entry.getValue();
+ short blockHash = entry.getKey();
+ int x = (blockHash >> 12 & 0xF) + bx;
+ int y = (blockHash & 0xFF);
+ int z = (blockHash >> 8 & 0xF) + bz;
+ BlockPos pos = new BlockPos(x, y, z); // Set pos
+ TileEntity tileEntity = nmsWorld.getTileEntity(pos);
+ if (tileEntity != null) {
+ NBTTagCompound tag = (NBTTagCompound) ForgeQueue_All.methodFromNative.invoke(null, nativeTag);
+ tag.setInteger("x", pos.getX());
+ tag.setInteger("y", pos.getY());
+ tag.setInteger("z", pos.getZ());
+ tileEntity.readFromNBT(tag); // ReadTagIntoTile
+ }
+ }
+ sectionPalettes = null;
+ } catch (Throwable e) {
+ MainUtil.handleError(e);
+ }
+ return this;
+ }
+}
diff --git a/forge112/src/main/java/com/boydti/fawe/forge/v112/ForgeQueue_All.java b/forge112/src/main/java/com/boydti/fawe/forge/v112/ForgeQueue_All.java
new file mode 100644
index 00000000..0b247bef
--- /dev/null
+++ b/forge112/src/main/java/com/boydti/fawe/forge/v112/ForgeQueue_All.java
@@ -0,0 +1,663 @@
+package com.boydti.fawe.forge.v112;
+
+import com.boydti.fawe.FaweCache;
+import com.boydti.fawe.example.CharFaweChunk;
+import com.boydti.fawe.example.NMSMappedFaweQueue;
+import com.boydti.fawe.forge.ForgePlayer;
+import com.boydti.fawe.forge.MutableGenLayer;
+import com.boydti.fawe.object.FaweChunk;
+import com.boydti.fawe.object.FawePlayer;
+import com.boydti.fawe.object.brush.visualization.VisualChunk;
+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.sk89q.jnbt.CompoundTag;
+import com.sk89q.jnbt.StringTag;
+import com.sk89q.jnbt.Tag;
+import com.sk89q.worldedit.world.biome.BaseBiome;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayDeque;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.atomic.LongAdder;
+import net.minecraft.block.Block;
+import net.minecraft.block.BlockFalling;
+import net.minecraft.block.state.IBlockState;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.EntityList;
+import net.minecraft.entity.player.EntityPlayer;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.init.Blocks;
+import net.minecraft.nbt.NBTBase;
+import net.minecraft.nbt.NBTTagCompound;
+import net.minecraft.network.PacketBuffer;
+import net.minecraft.network.play.server.SPacketChunkData;
+import net.minecraft.network.play.server.SPacketMultiBlockChange;
+import net.minecraft.server.management.PlayerChunkMap;
+import net.minecraft.server.management.PlayerChunkMapEntry;
+import net.minecraft.tileentity.TileEntity;
+import net.minecraft.util.ClassInheritanceMultiMap;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.ChunkPos;
+import net.minecraft.world.EnumSkyBlock;
+import net.minecraft.world.World;
+import net.minecraft.world.WorldServer;
+import net.minecraft.world.biome.BiomeCache;
+import net.minecraft.world.biome.BiomeProvider;
+import net.minecraft.world.chunk.BlockStateContainer;
+import net.minecraft.world.chunk.Chunk;
+import net.minecraft.world.chunk.IChunkProvider;
+import net.minecraft.world.chunk.NibbleArray;
+import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
+import net.minecraft.world.gen.ChunkGeneratorOverworld;
+import net.minecraft.world.gen.ChunkProviderServer;
+import net.minecraft.world.gen.IChunkGenerator;
+import net.minecraft.world.storage.WorldInfo;
+import net.minecraftforge.common.DimensionManager;
+
+public class ForgeQueue_All extends NMSMappedFaweQueue {
+
+ protected final static Method methodFromNative;
+ protected final static Method methodToNative;
+ protected final static Field fieldTickingBlockCount;
+ protected final static Field fieldNonEmptyBlockCount;
+
+ protected static Field fieldBiomes;
+ protected static Field fieldChunkGenerator;
+ protected static Field fieldSeed;
+ protected static Field fieldBiomeCache;
+ protected static Field fieldBiomes2;
+ protected static Field fieldGenLayer1;
+ protected static Field fieldGenLayer2;
+ protected static ExtendedBlockStorage emptySection;
+ private static MutableGenLayer genLayer;
+
+ static {
+ try {
+ emptySection = new ExtendedBlockStorage(0, true);
+ Class> converter = Class.forName("com.sk89q.worldedit.forge.NBTConverter");
+ methodFromNative = converter.getDeclaredMethod("toNative", Tag.class);
+ methodToNative = converter.getDeclaredMethod("fromNative", NBTBase.class);
+ methodFromNative.setAccessible(true);
+ methodToNative.setAccessible(true);
+
+ fieldBiomes = ChunkGeneratorOverworld.class.getDeclaredField("field_185981_C"); // biomesForGeneration
+ fieldBiomes.setAccessible(true);
+ fieldChunkGenerator = ChunkProviderServer.class.getDeclaredField("field_186029_c"); // chunkGenerator
+ fieldChunkGenerator.setAccessible(true);
+ fieldSeed = WorldInfo.class.getDeclaredField("field_76100_a"); // randomSeed
+ fieldSeed.setAccessible(true);
+ fieldBiomeCache = BiomeProvider.class.getDeclaredField("field_76942_f"); // biomeCache
+ fieldBiomeCache.setAccessible(true);
+ fieldBiomes2 = BiomeProvider.class.getDeclaredField("field_76943_g"); // biomesToSpawnIn
+ fieldBiomes2.setAccessible(true);
+ fieldGenLayer1 = BiomeProvider.class.getDeclaredField("field_76944_d"); // genBiomes
+ fieldGenLayer2 = BiomeProvider.class.getDeclaredField("field_76945_e"); // biomeIndexLayer
+ fieldGenLayer1.setAccessible(true);
+ fieldGenLayer2.setAccessible(true);
+
+ fieldTickingBlockCount = ExtendedBlockStorage.class.getDeclaredField("field_76683_c");
+ fieldNonEmptyBlockCount = ExtendedBlockStorage.class.getDeclaredField("field_76682_b");
+ fieldTickingBlockCount.setAccessible(true);
+ fieldNonEmptyBlockCount.setAccessible(true);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public ForgeQueue_All(com.sk89q.worldedit.world.World world) {
+ super(world);
+ getImpWorld();
+ }
+
+ public ForgeQueue_All(String world) {
+ super(world);
+ getImpWorld();
+ }
+
+ @Override
+ public void saveChunk(Chunk chunk) {
+ chunk.setModified(true);
+ }
+
+ @Override
+ public ExtendedBlockStorage[] getSections(Chunk chunk) {
+ return chunk.getBlockStorageArray();
+ }
+
+ @Override
+ public int getBiome(Chunk chunk, int x, int z) {
+ return chunk.getBiomeArray()[((z & 15) << 4) + (x & 15)];
+ }
+
+ @Override
+ public Chunk loadChunk(World world, int x, int z, boolean generate) {
+ ChunkProviderServer provider = (ChunkProviderServer) world.getChunkProvider();
+ if (generate) {
+ return provider.provideChunk(x, z);
+ } else {
+ return provider.loadChunk(x, z);
+ }
+ }
+
+ @Override
+ public ExtendedBlockStorage[] getCachedSections(World world, int cx, int cz) {
+ Chunk chunk = world.getChunkProvider().getLoadedChunk(cx, cz);
+ if (chunk != null) {
+ return chunk.getBlockStorageArray();
+ }
+ return null;
+ }
+
+ @Override
+ public Chunk getCachedChunk(World world, int cx, int cz) {
+ return world.getChunkProvider().getLoadedChunk(cx, cz);
+ }
+
+ @Override
+ public ExtendedBlockStorage getCachedSection(ExtendedBlockStorage[] ExtendedBlockStorages, int cy) {
+ return ExtendedBlockStorages[cy];
+ }
+
+ @Override
+ public void sendBlockUpdate(FaweChunk chunk, FawePlayer... players) {
+ try {
+ PlayerChunkMap playerManager = ((WorldServer) getWorld()).getPlayerChunkMap();
+ boolean watching = false;
+ boolean[] watchingArr = new boolean[players.length];
+ for (int i = 0; i < players.length; i++) {
+ EntityPlayerMP player = (EntityPlayerMP) ((ForgePlayer) players[i]).parent;
+ if (playerManager.isPlayerWatchingChunk(player, chunk.getX(), chunk.getZ())) {
+ watchingArr[i] = true;
+ watching = true;
+ }
+ }
+ if (!watching) return;
+ final LongAdder size = new LongAdder();
+ if (chunk instanceof VisualChunk) {
+ size.add(((VisualChunk) chunk).size());
+ } else if (chunk instanceof CharFaweChunk) {
+ size.add(((CharFaweChunk) chunk).getTotalCount());
+ } else {
+ chunk.forEachQueuedBlock(new FaweChunkVisitor() {
+ @Override
+ public void run(int localX, int y, int localZ, int combined) {
+ size.add(1);
+ }
+ });
+ }
+ if (size.intValue() == 0) return;
+ SPacketMultiBlockChange packet = new SPacketMultiBlockChange();
+ ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer();
+ final PacketBuffer buffer = new PacketBuffer(byteBuf);
+ buffer.writeInt(chunk.getX());
+ buffer.writeInt(chunk.getZ());
+ buffer.writeVarInt(size.intValue());
+ chunk.forEachQueuedBlock(new FaweChunkVisitor() {
+ @Override
+ public void run(int localX, int y, int localZ, int combined) {
+ short index = (short) (localX << 12 | localZ << 8 | y);
+ buffer.writeShort(index);
+ buffer.writeVarInt(combined);
+ }
+ });
+ packet.readPacketData(buffer);
+ for (int i = 0; i < players.length; i++) {
+ if (watchingArr[i]) ((EntityPlayerMP) ((ForgePlayer) players[i]).parent).connection.sendPacket(packet);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void setHeightMap(FaweChunk chunk, byte[] heightMap) {
+ Chunk forgeChunk = (Chunk) chunk.getChunk();
+ if (forgeChunk != null) {
+ int[] otherMap = forgeChunk.getHeightMap();
+ for (int i = 0; i < heightMap.length; i++) {
+ int newHeight = heightMap[i] & 0xFF;
+ int currentHeight = otherMap[i];
+ if (newHeight > currentHeight) {
+ otherMap[i] = newHeight;
+ }
+ }
+ }
+ }
+
+ protected BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(0, 0, 0);
+
+ @Override
+ public CompoundTag getTileEntity(Chunk chunk, int x, int y, int z) {
+ Map tiles = chunk.getTileEntityMap();
+ pos.setPos(x, y, z);
+ TileEntity tile = tiles.get(pos);
+ return tile != null ? getTag(tile) : null;
+ }
+
+ public CompoundTag getTag(TileEntity tile) {
+ try {
+ NBTTagCompound tag = new NBTTagCompound();
+ tile.writeToNBT(tag); // readTagIntoEntity
+ CompoundTag result = (CompoundTag) methodToNative.invoke(null, tag);
+ return result;
+ } catch (Exception e) {
+ MainUtil.handleError(e);
+ return null;
+ }
+ }
+
+ @Override
+ public boolean regenerateChunk(net.minecraft.world.World world, int x, int z, BaseBiome biome, Long seed) {
+ if (biome != null) {
+ try {
+ if (seed == null) {
+ seed = world.getSeed();
+ }
+ nmsWorld.getWorldInfo().getSeed();
+ boolean result;
+ ChunkGeneratorOverworld generator = new ChunkGeneratorOverworld(nmsWorld, seed, false, "");
+ net.minecraft.world.biome.Biome base = net.minecraft.world.biome.Biome.getBiome(biome.getId());
+ net.minecraft.world.biome.Biome[] existingBiomes = new net.minecraft.world.biome.Biome[256];
+ Arrays.fill(existingBiomes, base);
+ fieldBiomes.set(generator, existingBiomes);
+ boolean cold = base.getTemperature() <= 1;
+ IChunkGenerator existingGenerator = (IChunkGenerator) fieldChunkGenerator.get(nmsWorld.getChunkProvider());
+ long existingSeed = world.getSeed();
+ {
+ if (genLayer == null) genLayer = new MutableGenLayer(seed);
+ genLayer.set(biome.getId());
+ Object existingGenLayer1 = fieldGenLayer1.get(nmsWorld.provider.getBiomeProvider());
+ Object existingGenLayer2 = fieldGenLayer2.get(nmsWorld.provider.getBiomeProvider());
+ fieldGenLayer1.set(nmsWorld.provider.getBiomeProvider(), genLayer);
+ fieldGenLayer2.set(nmsWorld.provider.getBiomeProvider(), genLayer);
+
+ fieldSeed.set(nmsWorld.getWorldInfo(), seed);
+
+ ReflectionUtils.setFailsafeFieldValue(fieldBiomeCache, this.nmsWorld.provider.getBiomeProvider(), new BiomeCache(this.nmsWorld.provider.getBiomeProvider()));
+
+ ReflectionUtils.setFailsafeFieldValue(fieldChunkGenerator, this.nmsWorld.getChunkProvider(), generator);
+
+ result = regenerateChunk(world, x, z);
+
+ ReflectionUtils.setFailsafeFieldValue(fieldChunkGenerator, this.nmsWorld.getChunkProvider(), existingGenerator);
+
+ fieldSeed.set(nmsWorld.getWorldInfo(), existingSeed);
+
+ fieldGenLayer1.set(nmsWorld.provider.getBiomeProvider(), existingGenLayer1);
+ fieldGenLayer2.set(nmsWorld.provider.getBiomeProvider(), existingGenLayer2);
+ }
+ return result;
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ }
+ return regenerateChunk(world, x, z);
+ }
+
+ public boolean regenerateChunk(World world, int x, int z) {
+ IChunkProvider provider = world.getChunkProvider();
+ if (!(provider instanceof ChunkProviderServer)) {
+ return false;
+ }
+ BlockFalling.fallInstantly = true;
+ try {
+ ChunkProviderServer chunkServer = (ChunkProviderServer) provider;
+ IChunkGenerator gen = (IChunkGenerator) fieldChunkGenerator.get(chunkServer);
+ long pos = ChunkPos.asLong(x, z);
+ Chunk mcChunk;
+ if (chunkServer.chunkExists(x, z)) {
+ mcChunk = chunkServer.loadChunk(x, z);
+ mcChunk.onUnload();
+ }
+ PlayerChunkMap playerManager = ((WorldServer) getWorld()).getPlayerChunkMap();
+ List oldWatchers = null;
+ if (chunkServer.chunkExists(x, z)) {
+ mcChunk = chunkServer.loadChunk(x, z);
+ PlayerChunkMapEntry entry = playerManager.getEntry(x, z);
+ if (entry != null) {
+ Field fieldPlayers = PlayerChunkMapEntry.class.getDeclaredField("field_187283_c");
+ fieldPlayers.setAccessible(true);
+ oldWatchers = (List) fieldPlayers.get(entry);
+ playerManager.removeEntry(entry);
+ }
+ mcChunk.onUnload();
+ }
+ try {
+ Field droppedChunksSetField = chunkServer.getClass().getDeclaredField("field_73248_b");
+ droppedChunksSetField.setAccessible(true);
+ Set droppedChunksSet = (Set) droppedChunksSetField.get(chunkServer);
+ droppedChunksSet.remove(pos);
+ } catch (Throwable e) {
+ MainUtil.handleError(e);
+ }
+ Long2ObjectMap id2ChunkMap = chunkServer.id2ChunkMap;
+ id2ChunkMap.remove(pos);
+ mcChunk = gen.generateChunk(x, z);
+ id2ChunkMap.put(pos, mcChunk);
+ if (mcChunk != null) {
+ mcChunk.onLoad();
+ mcChunk.populate(chunkServer, gen);
+ }
+ if (oldWatchers != null) {
+ for (EntityPlayerMP player : oldWatchers) {
+ playerManager.addPlayer(player);
+ }
+ }
+ return true;
+ } catch (Throwable t) {
+ MainUtil.handleError(t);
+ return false;
+ } finally {
+ BlockFalling.fallInstantly = false;
+ }
+ }
+
+ @Override
+ public int getCombinedId4Data(ExtendedBlockStorage section, int x, int y, int z) {
+ IBlockState ibd = section.getData().get(x & 15, y & 15, z & 15);
+ Block block = ibd.getBlock();
+ int id = Block.getIdFromBlock(block);
+ if (FaweCache.hasData(id)) {
+ return (id << 4) + block.getMetaFromState(ibd);
+ } else {
+ return id << 4;
+ }
+ }
+
+ public int getNonEmptyBlockCount(ExtendedBlockStorage section) throws IllegalAccessException {
+ return (int) fieldNonEmptyBlockCount.get(section);
+ }
+
+ public void setCount(int tickingBlockCount, int nonEmptyBlockCount, ExtendedBlockStorage section) throws NoSuchFieldException, IllegalAccessException {
+ fieldTickingBlockCount.set(section, tickingBlockCount);
+ fieldNonEmptyBlockCount.set(section, nonEmptyBlockCount);
+ }
+
+ @Override
+ public CharFaweChunk getPrevious(CharFaweChunk fs, ExtendedBlockStorage[] sections, Map, ?> tilesGeneric, Collection>[] entitiesGeneric, Set createdEntities, boolean all) throws Exception {
+ Map tiles = (Map) tilesGeneric;
+ ClassInheritanceMultiMap[] entities = (ClassInheritanceMultiMap[]) entitiesGeneric;
+ CharFaweChunk previous = (CharFaweChunk) getFaweChunk(fs.getX(), fs.getZ());
+ char[][] idPrevious = previous.getCombinedIdArrays();
+ for (int layer = 0; layer < sections.length; layer++) {
+ if (fs.getCount(layer) != 0 || all) {
+ ExtendedBlockStorage section = sections[layer];
+ if (section != null) {
+ short solid = 0;
+ char[] previousLayer = idPrevious[layer] = new char[4096];
+ BlockStateContainer blocks = section.getData();
+ for (int j = 0; j < 4096; j++) {
+ int x = FaweCache.CACHE_X[0][j];
+ int y = FaweCache.CACHE_Y[0][j];
+ int z = FaweCache.CACHE_Z[0][j];
+ IBlockState ibd = blocks.get(x, y, z);
+ Block block = ibd.getBlock();
+ int combined = Block.getIdFromBlock(block);
+ if (FaweCache.hasData(combined)) {
+ combined = (combined << 4) + block.getMetaFromState(ibd);
+ } else {
+ combined = combined << 4;
+ }
+ if (combined > 1) {
+ solid++;
+ }
+ previousLayer[j] = (char) combined;
+ }
+ previous.count[layer] = solid;
+ previous.air[layer] = (short) (4096 - solid);
+ }
+ }
+ }
+ if (tiles != null) {
+ for (Map.Entry entry : tiles.entrySet()) {
+ TileEntity tile = entry.getValue();
+ NBTTagCompound tag = new NBTTagCompound();
+ tile.writeToNBT(tag); // readTileEntityIntoTag
+ BlockPos pos = entry.getKey();
+ CompoundTag nativeTag = (CompoundTag) methodToNative.invoke(null, tag);
+ previous.setTile(pos.getX(), pos.getY(), pos.getZ(), nativeTag);
+ }
+ }
+ if (entities != null) {
+ for (Collection entityList : entities) {
+ for (Entity ent : entityList) {
+ if (ent instanceof EntityPlayer || (!createdEntities.isEmpty() && createdEntities.contains(ent.getUniqueID()))) {
+ continue;
+ }
+ int x = ((int) Math.round(ent.posX) & 15);
+ int z = ((int) Math.round(ent.posZ) & 15);
+ int y = (int) Math.round(ent.posY);
+ int i = FaweCache.CACHE_I[y][z][x];
+ char[] array = fs.getIdArray(i);
+ if (array == null) {
+ continue;
+ }
+ int j = FaweCache.CACHE_J[y][z][x];
+ if (array[j] != 0) {
+ String id = EntityList.getEntityString(ent);
+ if (id != null) {
+ NBTTagCompound tag = ent.getEntityData(); // readEntityIntoTag
+ CompoundTag nativeTag = (CompoundTag) methodToNative.invoke(null, tag);
+ Map map = ReflectionUtils.getMap(nativeTag.getValue());
+ map.put("Id", new StringTag(id));
+ previous.setEntity(nativeTag);
+ }
+ }
+ }
+ }
+ }
+ return previous;
+ }
+
+ protected final static IBlockState air = Blocks.AIR.getDefaultState();
+
+ public void setPalette(ExtendedBlockStorage section, BlockStateContainer palette) throws NoSuchFieldException, IllegalAccessException {
+ Field fieldSection = ExtendedBlockStorage.class.getDeclaredField("data");
+ fieldSection.setAccessible(true);
+ fieldSection.set(section, palette);
+ }
+
+ @Override
+ public void sendChunk(int x, int z, int bitMask) {
+ Chunk chunk = getCachedChunk(getWorld(), x, z);
+ if (chunk != null) {
+ sendChunk(chunk, bitMask);
+ }
+ }
+
+ @Override
+ public void refreshChunk(FaweChunk fc) {
+ Chunk chunk = getCachedChunk(getWorld(), fc.getX(), fc.getZ());
+ if (chunk != null) {
+ sendChunk(chunk, fc.getBitMask());
+ }
+ }
+
+ public void sendChunk(Chunk nmsChunk, int mask) {
+ if (!nmsChunk.isLoaded()) {
+ return;
+ }
+ try {
+ ChunkPos pos = nmsChunk.getPos();
+ WorldServer w = (WorldServer) nmsChunk.getWorld();
+ PlayerChunkMap chunkMap = w.getPlayerChunkMap();
+ int x = pos.x;
+ int z = pos.z;
+ PlayerChunkMapEntry chunkMapEntry = chunkMap.getEntry(x, z);
+ if (chunkMapEntry == null) {
+ return;
+ }
+ final ArrayDeque players = new ArrayDeque<>();
+ chunkMapEntry.hasPlayerMatching(input -> {
+ players.add(input);
+ return false;
+ });
+ boolean empty = false;
+ ExtendedBlockStorage[] sections = nmsChunk.getBlockStorageArray();
+ for (int i = 0; i < sections.length; i++) {
+ if (sections[i] == null) {
+ sections[i] = emptySection;
+ empty = true;
+ }
+ }
+ if (mask == 0 || mask == 65535 && hasEntities(nmsChunk)) {
+ SPacketChunkData packet = new SPacketChunkData(nmsChunk, 65280);
+ for (EntityPlayerMP player : players) {
+ player.connection.sendPacket(packet);
+ }
+ mask = 255;
+ }
+ SPacketChunkData packet = new SPacketChunkData(nmsChunk, mask);
+ for (EntityPlayerMP player : players) {
+ player.connection.sendPacket(packet);
+ }
+ if (empty) {
+ for (int i = 0; i < sections.length; i++) {
+ if (sections[i] == emptySection) {
+ sections[i] = null;
+ }
+ }
+ }
+ } catch (Throwable e) {
+ MainUtil.handleError(e);
+ }
+ }
+
+ public boolean hasEntities(Chunk nmsChunk) {
+ ClassInheritanceMultiMap[] entities = nmsChunk.getEntityLists();
+ for (int i = 0; i < entities.length; i++) {
+ ClassInheritanceMultiMap slice = entities[i];
+ if (slice != null && !slice.isEmpty()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ @Override
+ public FaweChunk getFaweChunk(int x, int z) {
+ return new ForgeChunk_All(this, x, z);
+ }
+
+ @Override
+ public boolean removeLighting(ExtendedBlockStorage[] sections, RelightMode mode, boolean sky) {
+ if (mode == RelightMode.ALL) {
+ for (int i = 0; i < sections.length; i++) {
+ ExtendedBlockStorage section = sections[i];
+ if (section != null) {
+ section.setBlockLight(new NibbleArray());
+ if (sky) {
+ section.setSkyLight(new NibbleArray());
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean hasSky() {
+ return nmsWorld.provider.hasSkyLight();
+ }
+
+ @Override
+ public void setFullbright(ExtendedBlockStorage[] sections) {
+ for (int i = 0; i < sections.length; i++) {
+ ExtendedBlockStorage section = sections[i];
+ if (section != null) {
+ byte[] bytes = section.getSkyLight().getData();
+ Arrays.fill(bytes, (byte) 255);
+ }
+ }
+ }
+
+ @Override
+ public void relight(int x, int y, int z) {
+ pos.setPos(x, y, z);
+ nmsWorld.checkLight(pos);
+ }
+
+ protected WorldServer nmsWorld;
+
+ @Override
+ public World getImpWorld() {
+ if (nmsWorld != null || getWorldName() == null) {
+ return nmsWorld;
+ }
+ String[] split = getWorldName().split(";");
+ int id = Integer.parseInt(split[split.length - 1]);
+ nmsWorld = DimensionManager.getWorld(id);
+ return nmsWorld;
+ }
+
+ @Override
+ public void setSkyLight(ExtendedBlockStorage section, int x, int y, int z, int value) {
+ section.getSkyLight().set(x & 15, y & 15, z & 15, value);
+ }
+
+ @Override
+ public void setBlockLight(ExtendedBlockStorage section, int x, int y, int z, int value) {
+ section.getBlockLight().set(x & 15, y & 15, z & 15, value);
+ }
+
+ @Override
+ public int getSkyLight(ExtendedBlockStorage section, int x, int y, int z) {
+ return section.getSkyLight(x & 15, y & 15, z & 15);
+ }
+
+ @Override
+ public int getEmmittedLight(ExtendedBlockStorage section, int x, int y, int z) {
+ return section.getBlockLight(x & 15, y & 15, z & 15);
+ }
+
+ @Override
+ public int getOpacity(ExtendedBlockStorage section, int x, int y, int z) {
+ BlockStateContainer dataPalette = section.getData();
+ IBlockState ibd = dataPalette.get(x & 15, y & 15, z & 15);
+ return ibd.getLightOpacity();
+ }
+
+ @Override
+ public int getBrightness(ExtendedBlockStorage section, int x, int y, int z) {
+ BlockStateContainer dataPalette = section.getData();
+ IBlockState ibd = dataPalette.get(x & 15, y & 15, z & 15);
+ return ibd.getLightValue();
+ }
+
+ @Override
+ public int getOpacityBrightnessPair(ExtendedBlockStorage section, int x, int y, int z) {
+ BlockStateContainer dataPalette = section.getData();
+ IBlockState ibd = dataPalette.get(x & 15, y & 15, z & 15);
+ return MathMan.pair16(ibd.getLightOpacity(), ibd.getLightValue());
+ }
+
+ @Override
+ public void relightBlock(int x, int y, int z) {
+ pos.setPos(x, y, z);
+ nmsWorld.checkLightFor(EnumSkyBlock.BLOCK, pos);
+ }
+
+ @Override
+ public void relightSky(int x, int y, int z) {
+ pos.setPos(x, y, z);
+ nmsWorld.checkLightFor(EnumSkyBlock.SKY, pos);
+ }
+
+ @Override
+ public File getSaveFolder() {
+ return new File(((WorldServer) getWorld()).getChunkSaveLocation(), "region");
+ }
+}
diff --git a/forge112/src/main/java/com/sk89q/worldedit/forge/ForgePlayer.java b/forge112/src/main/java/com/sk89q/worldedit/forge/ForgePlayer.java
new file mode 100644
index 00000000..9a13da4d
--- /dev/null
+++ b/forge112/src/main/java/com/sk89q/worldedit/forge/ForgePlayer.java
@@ -0,0 +1,231 @@
+/*
+ * WorldEdit, a Minecraft world manipulation toolkit
+ * Copyright (C) sk89q
+ * Copyright (C) WorldEdit team and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.sk89q.worldedit.forge;
+
+import com.boydti.fawe.forge.ForgePlayerBlockBag;
+import com.sk89q.util.StringUtil;
+import com.sk89q.worldedit.EditSession;
+import com.sk89q.worldedit.Vector;
+import com.sk89q.worldedit.WorldVector;
+import com.sk89q.worldedit.blocks.BaseBlock;
+import com.sk89q.worldedit.entity.BaseEntity;
+import com.sk89q.worldedit.extension.platform.AbstractPlayerActor;
+import com.sk89q.worldedit.extent.inventory.BlockBag;
+import com.sk89q.worldedit.internal.LocalWorldAdapter;
+import com.sk89q.worldedit.internal.cui.CUIEvent;
+import com.sk89q.worldedit.session.SessionKey;
+import com.sk89q.worldedit.util.Location;
+import io.netty.buffer.Unpooled;
+import java.util.UUID;
+import javax.annotation.Nullable;
+import net.minecraft.entity.player.EntityPlayerMP;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemStack;
+import net.minecraft.network.PacketBuffer;
+import net.minecraft.network.play.server.SPacketCustomPayload;
+import net.minecraft.util.EnumHand;
+import net.minecraft.util.text.TextComponentString;
+import net.minecraft.util.text.TextFormatting;
+
+public class ForgePlayer extends AbstractPlayerActor {
+
+ private final EntityPlayerMP player;
+
+ protected ForgePlayer(EntityPlayerMP player) {
+ this.player = player;
+ ThreadSafeCache.getInstance().getOnlineIds().add(getUniqueId());
+ }
+
+ @Override
+ public UUID getUniqueId() {
+ return player.getUniqueID();
+ }
+
+ @Override
+ public int getItemInHand() {
+ ItemStack is = this.player.getHeldItem(EnumHand.MAIN_HAND);
+ return is == null ? 0 : Item.getIdFromItem(is.getItem());
+ }
+
+ @Override
+ public BaseBlock getBlockInHand() {
+ ItemStack is = this.player.getHeldItem(EnumHand.MAIN_HAND);
+ return is == null ? EditSession.nullBlock : new BaseBlock(Item.getIdFromItem(is.getItem()), is.isItemStackDamageable() ? 0 : is.getItemDamage());
+ }
+
+ @Override
+ public String getName() {
+ return this.player.getName();
+ }
+
+ @Override
+ public BaseEntity getState() {
+ throw new UnsupportedOperationException("Cannot create a state from this object");
+ }
+
+ @Override
+ public Location getLocation() {
+ Vector position = new Vector(this.player.posX, this.player.posY, this.player.posZ);
+ return new Location(
+ ForgeWorldEdit.inst.getWorld(this.player.world),
+ position,
+ this.player.rotationYaw,
+ this.player.rotationPitch);
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public WorldVector getPosition() {
+ return new WorldVector(LocalWorldAdapter.adapt(ForgeWorldEdit.inst.getWorld(this.player.world)), this.player.posX, this.player.posY, this.player.posZ);
+ }
+
+ @Override
+ public com.sk89q.worldedit.world.World getWorld() {
+ return ForgeWorldEdit.inst.getWorld(this.player.world);
+ }
+
+ @Override
+ public double getPitch() {
+ return this.player.rotationPitch;
+ }
+
+ @Override
+ public double getYaw() {
+ return this.player.rotationYaw;
+ }
+
+ @Override
+ public void giveItem(int type, int amt) {
+ this.player.inventory.addItemStackToInventory(new ItemStack(Item.getItemById(type), amt, 0));
+ }
+
+ @Override
+ public void dispatchCUIEvent(CUIEvent event) {
+ String[] params = event.getParameters();
+ String send = event.getTypeId();
+ if (params.length > 0) {
+ send = send + "|" + StringUtil.joinString(params, "|");
+ }
+ PacketBuffer buffer = new PacketBuffer(Unpooled.copiedBuffer(send.getBytes(WECUIPacketHandler.UTF_8_CHARSET)));
+ SPacketCustomPayload packet = new SPacketCustomPayload(ForgeWorldEdit.CUI_PLUGIN_CHANNEL, buffer);
+ this.player.connection.sendPacket(packet);
+ }
+
+ @Override
+ public void printRaw(String msg) {
+ for (String part : msg.split("\n")) {
+ this.player.sendMessage(new TextComponentString(part));
+ }
+ }
+
+ @Override
+ public void printDebug(String msg) {
+ sendColorized(msg, TextFormatting.GRAY);
+ }
+
+ @Override
+ public void print(String msg) {
+ sendColorized(msg, TextFormatting.LIGHT_PURPLE);
+ }
+
+ @Override
+ public void printError(String msg) {
+ sendColorized(msg, TextFormatting.RED);
+ }
+
+ private void sendColorized(String msg, TextFormatting formatting) {
+ for (String part : msg.split("\n")) {
+ TextComponentString component = new TextComponentString(part);
+ component.getStyle().setColor(formatting);
+ this.player.sendMessage(component);
+ }
+ }
+
+ @Override
+ public void setPosition(Vector pos, float pitch, float yaw) {
+ this.player.connection.setPlayerLocation(pos.getX(), pos.getY(), pos.getZ(), yaw, pitch);
+ }
+
+ @Override
+ public String[] getGroups() {
+ return new String[]{}; // WorldEditMod.inst.getPermissionsResolver().getGroups(this.player.username);
+ }
+
+ @Override
+ public BlockBag getInventoryBlockBag() {
+ return new ForgePlayerBlockBag(player);
+ }
+
+ @Override
+ public boolean hasPermission(String perm) {
+ return ForgeWorldEdit.inst.getPermissionsProvider().hasPermission(player, perm);
+ }
+
+ @Nullable
+ @Override
+ public T getFacet(Class extends T> cls) {
+ return null;
+ }
+
+ @Override
+ public SessionKey getSessionKey() {
+ return new SessionKeyImpl(player.getUniqueID(), player.getName());
+ }
+
+ private static class SessionKeyImpl implements SessionKey {
+ // If not static, this will leak a reference
+
+ private final UUID uuid;
+ private final String name;
+
+ private SessionKeyImpl(UUID uuid, String name) {
+ this.uuid = uuid;
+ this.name = name;
+ }
+
+ @Override
+ public UUID getUniqueId() {
+ return uuid;
+ }
+
+ @Nullable
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public boolean isActive() {
+ // We can't directly check if the player is online because
+ // the list of players is not thread safe
+ return ThreadSafeCache.getInstance().getOnlineIds().contains(uuid);
+ }
+
+ @Override
+ public boolean isPersistent() {
+ return true;
+ }
+
+ }
+
+ public static Class inject() {
+ return ForgePlayer.class;
+ }
+}
\ No newline at end of file
diff --git a/nukkit/build.gradle b/nukkit/build.gradle
index 67a0c81d..22d339c3 100644
--- a/nukkit/build.gradle
+++ b/nukkit/build.gradle
@@ -3,8 +3,8 @@ repositories {
}
dependencies {
compile project(':core')
- compile group: "cn.nukkit", name: "nukkit", version: "1.0-20170704.231613-609", changing: true
- compile name: 'worldedit-core-6.1.4-SNAPSHOT-dist', changing: true
+ compile group: "cn.nukkit", name: "nukkit", version: "1.0-20170704.231613-609"
+ compile name: 'worldedit-core-6.1.4-SNAPSHOT-dist'
}
processResources {
@@ -33,6 +33,9 @@ shadowJar {
}
archiveName = "${parent.name}-${project.name}-${parent.version}.jar"
destinationDir = file '../target'
+ manifest {
+ attributes("Main-Class": "com.boydti.fawe.nukkit.core.converter.ConverterFrame")
+ }
relocate('com.google.gson', 'com.sk89q.worldedit.internal.gson')
relocate 'org.yaml.snakeyaml', 'com.boydti.fawe.yaml'
diff --git a/nukkit/src/main/java/com/boydti/fawe/nukkit/core/NukkitWorldEdit.java b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/NukkitWorldEdit.java
index 97742a25..69a1f64b 100644
--- a/nukkit/src/main/java/com/boydti/fawe/nukkit/core/NukkitWorldEdit.java
+++ b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/NukkitWorldEdit.java
@@ -26,6 +26,7 @@ import cn.nukkit.command.CommandSender;
import cn.nukkit.plugin.PluginBase;
import com.boydti.fawe.Fawe;
import com.boydti.fawe.config.Settings;
+import com.boydti.fawe.nukkit.core.converter.ConvertCommands;
import com.boydti.fawe.nukkit.optimization.FaweNukkit;
import com.google.common.base.Joiner;
import com.sk89q.util.yaml.YAMLProcessor;
@@ -34,6 +35,7 @@ import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.event.platform.CommandEvent;
import com.sk89q.worldedit.event.platform.PlatformReadyEvent;
import com.sk89q.worldedit.extension.platform.Actor;
+import com.sk89q.worldedit.extension.platform.CommandManager;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
@@ -84,6 +86,10 @@ public class NukkitWorldEdit extends PluginBase {
this.platform = new NukkitPlatform(this);
getServer().getPluginManager().registerEvents(new WorldEditListener(this), this);
WorldEdit.getInstance().getPlatformManager().register(platform);
+ {
+ CommandManager cmdMan = CommandManager.getInstance();
+ cmdMan.registerCommands(new ConvertCommands(WorldEdit.getInstance()));
+ }
logger.info("WorldEdit for Nukkit (version " + getInternalVersion() + ") is loaded");
WorldEdit.getInstance().getEventBus().post(new PlatformReadyEvent());
} catch (Throwable e) {
diff --git a/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/ConvertCommands.java b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/ConvertCommands.java
new file mode 100644
index 00000000..7c71c888
--- /dev/null
+++ b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/ConvertCommands.java
@@ -0,0 +1,65 @@
+package com.boydti.fawe.nukkit.core.converter;
+
+import com.boydti.fawe.command.AnvilCommands;
+import com.boydti.fawe.config.BBC;
+import com.boydti.fawe.jnbt.anvil.MCAChunk;
+import com.boydti.fawe.jnbt.anvil.MCAFile;
+import com.boydti.fawe.jnbt.anvil.MCAFilter;
+import com.boydti.fawe.jnbt.anvil.filters.DelegateMCAFilter;
+import com.boydti.fawe.jnbt.anvil.filters.RemapFilter;
+import com.boydti.fawe.object.FaweQueue;
+import com.boydti.fawe.object.RunnableVal;
+import com.boydti.fawe.object.clipboard.ClipboardRemapper;
+import com.boydti.fawe.object.number.MutableLong;
+import com.boydti.fawe.util.SetQueue;
+import com.sk89q.minecraft.util.commands.Command;
+import com.sk89q.minecraft.util.commands.CommandPermissions;
+import com.sk89q.worldedit.WorldEdit;
+import com.sk89q.worldedit.WorldEditException;
+import com.sk89q.worldedit.command.MethodCommands;
+import com.sk89q.worldedit.entity.Player;
+import com.sk89q.worldedit.util.command.binding.Switch;
+import java.io.IOException;
+
+public class ConvertCommands extends MethodCommands {
+ public ConvertCommands(WorldEdit worldEdit) {
+ super(worldEdit);
+ }
+
+ @Command(
+ aliases = {"anvil2leveldb"},
+ usage = "",
+ help = "Convert the world between MCPE/PC values\n",
+ desc = "Convert the world between MCPE/PC values\n",
+ min = 1,
+ max = 1
+ )
+ @CommandPermissions("worldedit.anvil.anvil2leveldb")
+ public void anvil2leveldb(Player player, String folder, @Switch('f') boolean force) throws WorldEditException {
+ ClipboardRemapper mapper;
+ RemapFilter filter = new RemapFilter(ClipboardRemapper.RemapPlatform.PC, ClipboardRemapper.RemapPlatform.PE);
+
+ FaweQueue defaultQueue = SetQueue.IMP.getNewQueue(folder, true, false);
+ try (MCAFile2LevelDB converter = new MCAFile2LevelDB(defaultQueue.getSaveFolder().getParentFile())) {
+
+ DelegateMCAFilter delegate = new DelegateMCAFilter(filter) {
+ @Override
+ public void finishFile(MCAFile file, MutableLong cache) {
+ file.forEachChunk(new RunnableVal() {
+ @Override
+ public void run(MCAChunk value) {
+ try {
+ converter.write(value);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ file.clear();
+ }
+ };
+ MCAFilter result = AnvilCommands.runWithWorld(player, folder, delegate, force);
+ if (result != null) player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(filter.getTotal()));
+ }
+ }
+}
diff --git a/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/ConverterFrame.java b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/ConverterFrame.java
new file mode 100644
index 00000000..2d056c8e
--- /dev/null
+++ b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/ConverterFrame.java
@@ -0,0 +1,395 @@
+package com.boydti.fawe.nukkit.core.converter;
+
+import com.boydti.fawe.Fawe;
+import com.boydti.fawe.FaweVersion;
+import com.boydti.fawe.installer.BrowseButton;
+import com.boydti.fawe.installer.CloseButton;
+import com.boydti.fawe.installer.ImagePanel;
+import com.boydti.fawe.installer.InteractiveButton;
+import com.boydti.fawe.installer.InvisiblePanel;
+import com.boydti.fawe.installer.MinimizeButton;
+import com.boydti.fawe.installer.MovablePanel;
+import com.boydti.fawe.installer.TextAreaOutputStream;
+import com.boydti.fawe.installer.URLButton;
+import com.boydti.fawe.jnbt.anvil.MCAFilter;
+import com.boydti.fawe.jnbt.anvil.MCAQueue;
+import com.boydti.fawe.util.MainUtil;
+import com.boydti.fawe.wrappers.FakePlayer;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Map;
+import javax.imageio.ImageIO;
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.SwingConstants;
+import javax.swing.border.EmptyBorder;
+
+public class ConverterFrame extends JFrame {
+ private final InvisiblePanel loggerPanel;
+ private Color LIGHT_GRAY = new Color(0x66, 0x66, 0x66);
+ private Color GRAY = new Color(0x44, 0x44, 0x46);
+ private Color DARK_GRAY = new Color(0x33, 0x33, 0x36);
+ private Color DARKER_GRAY = new Color(0x26, 0x26, 0x28);
+ private Color INVISIBLE = new Color(0, 0, 0, 0);
+ private Color OFF_WHITE = new Color(200, 200, 200);
+
+ private JTextArea loggerTextArea;
+ private BrowseButton browseLoad;
+ private BrowseButton browseSave;
+
+ public ConverterFrame() throws Exception {
+ final MovablePanel movable = new MovablePanel(this);
+ movable.setBorder(BorderFactory.createLineBorder(new Color(0x28, 0x28, 0x29)));
+
+ Container content = this.getContentPane();
+ content.add(movable);
+ this.setSize(720, 640);
+ this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ this.setUndecorated(true);
+ Dimension dimension = Toolkit.getDefaultToolkit().getScreenSize();
+ int x = (int) ((dimension.getWidth() - this.getWidth()) / 2);
+ int y = (int) ((dimension.getHeight() - this.getHeight()) / 2);
+ this.setLocation(x, y);
+ this.setVisible(true);
+ this.setOpacity(0);
+ movable.setBackground(DARK_GRAY);
+ movable.setLayout(new BorderLayout());
+
+ fadeIn();
+
+ JPanel topBar = new InvisiblePanel(new BorderLayout());
+ {
+ JPanel topBarLeft = new InvisiblePanel();
+ JPanel topBarCenter = new InvisiblePanel();
+ JPanel topBarRight = new InvisiblePanel();
+
+ JLabel title = new JLabel("(FAWE) Anvil to LevelDB converter");
+ title.setHorizontalAlignment(SwingConstants.CENTER);
+ title.setAlignmentX(Component.RIGHT_ALIGNMENT);
+ title.setForeground(Color.LIGHT_GRAY);
+ title.setFont(new Font("Lucida Sans Unicode", Font.PLAIN, 15));
+
+ MinimizeButton minimize = new MinimizeButton(this);
+ CloseButton exit = new CloseButton();
+
+ topBarLeft.setPreferredSize(new Dimension(96, 36));
+ try {
+ BufferedImage image = ImageIO.read(getClass().getResource("/axe-logo.png"));
+ setIconImage(image);
+ ImagePanel imgPanel = new ImagePanel(image);
+ imgPanel.setPreferredSize(new Dimension(32, 36));
+ topBarLeft.add(imgPanel);
+ } catch (IOException ignore) {}
+
+ topBarCenter.add(title);
+ topBarRight.add(minimize);
+ topBarRight.add(exit);
+
+ topBar.add(topBarLeft, BorderLayout.WEST);
+ topBar.add(topBarCenter, BorderLayout.CENTER);
+ topBar.add(topBarRight, BorderLayout.EAST);
+ }
+ final JPanel mainContent = new InvisiblePanel(new BorderLayout());
+ {
+ File world = MainUtil.getWorkingDirectory("minecraft");
+ if (world != null && world.exists()) {
+ File saves = new File(world, "saves");
+ if (saves.exists()) {
+ for (File file : saves.listFiles()) {
+ if (file.isDirectory()) {
+ world = file;
+ break;
+ }
+ }
+ }
+ }
+
+ final InteractiveButton browseLoadText = new InteractiveButton(world.getPath(), DARKER_GRAY) {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ browseLoad.browse(new File(getText()));
+ }
+ };
+ final InteractiveButton browseSaveText = new InteractiveButton(getDefaultOutput().getPath(), DARKER_GRAY) {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ browseSave.browse(new File(getText()));
+ }
+ };
+ for (JButton button : Arrays.asList(browseLoadText, browseSaveText)) {
+ button.setForeground(OFF_WHITE);
+ button.setBackground(DARKER_GRAY);
+ button.setOpaque(true);
+ button.setBorder(new EmptyBorder(4, 4, 4, 4));
+ }
+ browseLoad = new BrowseButton() {
+ @Override
+ public void onSelect(File folder) {
+ browseLoadText.setText(folder.getPath());
+ movable.repaint();
+ }
+ };
+ browseSave = new BrowseButton() {
+ @Override
+ public void onSelect(File folder) {
+ browseSaveText.setText(folder.getPath());
+ movable.repaint();
+ }
+ };
+
+ final JPanel browseContent = new InvisiblePanel(new BorderLayout());
+ final JPanel browseLoadContent = new InvisiblePanel(new BorderLayout());
+ final JPanel browseSaveContent = new InvisiblePanel(new BorderLayout());
+ browseSaveContent.setBorder(new EmptyBorder(10, 0, 0, 0));
+ JLabel selectWorld = new JLabel("Select World:");
+ selectWorld.setForeground(OFF_WHITE);
+ selectWorld.setPreferredSize(new Dimension(120, 0));
+ JLabel output = new JLabel("Output:");
+ output.setForeground(OFF_WHITE);
+ output.setPreferredSize(new Dimension(120, 0));
+
+ browseLoadContent.add(selectWorld, BorderLayout.WEST);
+ browseLoadContent.add(browseLoadText, BorderLayout.CENTER);
+ browseLoadContent.add(browseLoad, BorderLayout.EAST);
+ browseSaveContent.add(output, BorderLayout.WEST);
+ browseSaveContent.add(browseSaveText, BorderLayout.CENTER);
+ browseSaveContent.add(browseSave, BorderLayout.EAST);
+ browseContent.add(browseLoadContent, BorderLayout.NORTH);
+ browseContent.add(browseSaveContent, BorderLayout.SOUTH);
+
+ InteractiveButton install = new InteractiveButton(">> Convert World <<", DARKER_GRAY) {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ try {
+ install(browseLoadText.getText(), browseSaveText.getText());
+ } catch (Exception e1) {
+ e1.printStackTrace();
+ }
+ }
+ };
+
+ final JPanel installContent = new InvisiblePanel(new FlowLayout());
+ install.setPreferredSize(new Dimension(Integer.MAX_VALUE, 32));
+ installContent.add(install);
+ installContent.setBorder(new EmptyBorder(10, 0, 10, 0));
+ this.loggerPanel = new InvisiblePanel(new BorderLayout());
+ this.loggerPanel.setBackground(Color.GREEN);
+ loggerPanel.setPreferredSize(new Dimension(416, 442));
+ loggerTextArea = new JTextArea();
+ loggerTextArea.setBackground(Color.GRAY);
+ loggerTextArea.setForeground(Color.DARK_GRAY);
+ loggerTextArea.setFont(new Font(loggerTextArea.getFont().getName(), Font.BOLD, 9));
+ loggerTextArea.setBorder(BorderFactory.createCompoundBorder(loggerTextArea.getBorder(), BorderFactory.createEmptyBorder(6, 6, 6, 6)));
+ JScrollPane scroll = new JScrollPane(loggerTextArea, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+ scroll.setBackground(DARK_GRAY);
+ scroll.setBorder(new EmptyBorder(0, 0, 0, 0));
+ loggerPanel.add(scroll);
+ loggerPanel.setVisible(false);
+
+ mainContent.setBorder(new EmptyBorder(6, 32, 6, 32));
+ mainContent.add(browseContent, BorderLayout.NORTH);
+ mainContent.add(installContent, BorderLayout.CENTER);
+ mainContent.add(loggerPanel, BorderLayout.SOUTH);
+ }
+ JPanel bottomBar = new InvisiblePanel();
+ {
+ try {
+ InputStream stream = getClass().getResourceAsStream("/fawe.properties");
+ java.util.Scanner scanner = new java.util.Scanner(stream).useDelimiter("\\A");
+ String versionString = scanner.next().trim();
+ scanner.close();
+ FaweVersion version = new FaweVersion(versionString);
+ String date = new Date(100 + version.year, version.month, version.day).toGMTString();
+ String build = "https://ci.athion.net/job/FastAsyncWorldEdit/" + version.build;
+ String commit = "https://github.com/boy0001/FastAsyncWorldedit/commit/" + Integer.toHexString(version.hash);
+ String footerMessage = "FAWE v" + version.major + "." + version.minor + "." + version.patch + " by Empire92 (c) 2017 (GPL v3.0)";
+ URL licenseUrl = new URL("https://github.com/boy0001/FastAsyncWorldedit/blob/master/LICENSE");
+ URLButton licenseButton = new URLButton(licenseUrl, footerMessage);
+ bottomBar.add(licenseButton);
+ } catch (Throwable ignore) {
+ ignore.printStackTrace();
+ }
+ URL chat = new URL("https://discord.gg/ngZCzbU");
+ URLButton chatButton = new URLButton(chat, "Chat");
+ bottomBar.add(chatButton);
+ URL wiki = new URL("https://github.com/boy0001/FastAsyncWorldedit/wiki");
+ URLButton wikiButton = new URLButton(wiki, "Wiki");
+ bottomBar.add(wikiButton);
+ URL issue = new URL("https://github.com/boy0001/FastAsyncWorldedit/issues/new");
+ URLButton issueButton = new URLButton(issue, "Report Issue");
+ bottomBar.add(issueButton);
+ bottomBar.setBackground(new Color(0x26, 0x26, 0x28));
+
+ bottomBar.add(new InteractiveButton("Debug") {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ Map stacks = Thread.getAllStackTraces();
+ for (Map.Entry entry : stacks.entrySet()) {
+ Thread thread = entry.getKey();
+ Fawe.debug("--------------------------------------------------------------------------------------------");
+ Fawe.debug("Thread: " + thread.getName() + " | Id: " + thread.getId() + " | Alive: " + thread.isAlive());
+ for (StackTraceElement elem : entry.getValue()) {
+ Fawe.debug(elem.toString());
+ }
+ }
+ }
+ });
+ }
+
+ // We want to add these a bit later
+ movable.add(topBar, BorderLayout.NORTH);
+ this.setVisible(true);
+ this.repaint();
+ movable.add(mainContent, BorderLayout.CENTER);
+ this.setVisible(true);
+ this.repaint();
+ movable.add(bottomBar, BorderLayout.SOUTH);
+ this.setVisible(true);
+ this.repaint();
+ }
+
+ private File getDefaultOutput() {
+ if (MainUtil.getPlatform() == MainUtil.OS.WINDOWS) {
+ String applicationData = System.getenv("APPDATA");
+ if (applicationData != null && new File(applicationData).exists()) {
+ File saves = new File(new File(applicationData).getParentFile(), "Local/Packages/Microsoft.MinecraftUWP_8wekyb3d8bbwe/LocalState/games/com.mojang/minecraftWorlds");
+ if (saves.exists()) return saves.getAbsoluteFile();
+ }
+ }
+ return new File(".");
+ }
+
+ public void prompt(String message) {
+ JOptionPane.showMessageDialog(null, message);
+ }
+
+ public void debug(String m) {
+ System.out.println(m);
+ }
+
+ public void install(String input, String output) throws Exception {
+ if (!loggerPanel.isVisible()) {
+ loggerPanel.setVisible(true);
+ this.repaint();
+ TextAreaOutputStream logger = new TextAreaOutputStream(loggerTextArea);
+ System.setOut(logger);
+ System.setErr(logger);
+ }
+ if (input == null || input.isEmpty()) {
+ prompt("No world selected");
+ return;
+ }
+ if (output == null || output.isEmpty()) {
+ prompt("No output folder selection");
+ return;
+ }
+ if (new File(output, new File(input).getName()).exists()) {
+ prompt("Please select another output directory, or delete it, as there are files already there.");
+ return;
+ }
+ final File dirMc = new File(input);
+ if (!dirMc.exists()) {
+ prompt("Folder does not exist");
+ return;
+ }
+ if (!dirMc.isDirectory()) {
+ prompt("You must select a folder, not a file");
+ return;
+ }
+ Thread installThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ FakePlayer console = FakePlayer.getConsole();
+ try {
+ debug("Loading nukkit.jar");
+ File nukkit = new File("nukkit.jar");
+ if (!nukkit.exists()) {
+ debug("Downloading: http://ci.mengcraft.com:8080/job/nukkit/614/artifact/target/nukkit-1.0-SNAPSHOT.jar");
+ URL url = new URL("http://ci.mengcraft.com:8080/job/nukkit/614/artifact/target/nukkit-1.0-SNAPSHOT.jar");
+ ReadableByteChannel rbc = Channels.newChannel(url.openStream());
+ FileOutputStream fos = new FileOutputStream(nukkit);
+ fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
+ }
+ MainUtil.loadURLClasspath(nukkit.toURL());
+
+ File newWorldFile = new File(output, dirMc.getName());
+ try (MCAFile2LevelDB converter = new MCAFile2LevelDB(newWorldFile)) {
+ debug("Starting world conversion");
+
+ MCAFilter filter = converter.toFilter();
+ MCAQueue queue = new MCAQueue(null, new File(dirMc, "region"), true);
+ MCAFilter result = queue.filterWorld(filter);
+
+ File levelDat = new File(dirMc, "level.dat");
+ if (levelDat.exists()) {
+ converter.copyLevelDat(levelDat);
+ }
+ converter.close();
+ prompt(
+ "Conversion complete!\n" +
+ " - The world save is still being compacted, but you can close the program anytime\n" +
+ " - There will be another prompt when this finishes\n" +
+ "\n" +
+ "What is not converted?\n" +
+ " - Inventory is not copied\n" +
+ " - Some block nbt may not copy\n" +
+ " - Any custom generator settings may not work\n" +
+ " - May not match up with new terrain"
+ );
+ converter.compact();
+ prompt("Compaction complete!");
+ }
+ } catch (Throwable e) {
+ e.printStackTrace();
+ prompt("[ERROR] Conversion failed, you will have to do it manually (Nukkit server + anvil2leveldb command)");
+ return;
+ }
+ }
+ });
+ installThread.start();
+ }
+
+ public void fadeIn() {
+ Thread thread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ for (float i = 0; i <= 1.015; i += 0.016) {
+ ConverterFrame.this.setOpacity(Math.min(1, i));
+ try {
+ Thread.sleep(16);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ });
+ thread.start();
+ }
+
+ public static void main(String[] args) throws Exception {
+ ConverterFrame window = new ConverterFrame();
+ }
+}
diff --git a/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/LevelDBToMCAFile.java b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/LevelDBToMCAFile.java
new file mode 100644
index 00000000..f252cd05
--- /dev/null
+++ b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/LevelDBToMCAFile.java
@@ -0,0 +1,73 @@
+package com.boydti.fawe.nukkit.core.converter;
+
+import com.boydti.fawe.Fawe;
+import com.boydti.fawe.object.clipboard.ClipboardRemapper;
+import com.boydti.fawe.util.MemUtil;
+import com.sk89q.worldedit.world.registry.BundledBlockData;
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+import org.iq80.leveldb.DB;
+import org.iq80.leveldb.Options;
+import org.iq80.leveldb.impl.Iq80DBFactory;
+
+public class LevelDBToMCAFile implements Closeable, Runnable{
+
+ private final DB db;
+ private final ClipboardRemapper remapper;
+ private final ForkJoinPool pool;
+
+ public LevelDBToMCAFile(File folder) {
+ try {
+ this.pool = new ForkJoinPool();
+ this.remapper = new ClipboardRemapper(ClipboardRemapper.RemapPlatform.PC, ClipboardRemapper.RemapPlatform.PE);
+ BundledBlockData.getInstance().loadFromResource();
+ int bufferSize = (int) Math.min(Integer.MAX_VALUE, Math.max((long) (MemUtil.getFreeBytes() * 0.8), 134217728));
+ this.db = Iq80DBFactory.factory.open(new File(folder, "db"),
+ new Options()
+ .createIfMissing(false)
+ .verifyChecksums(false)
+ .blockSize(262144) // 256K
+ .cacheSize(bufferSize) // 8MB
+ );
+ try {
+ this.db.suspendCompactions();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void close() {
+ try {
+ pool.shutdown();
+ pool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
+ db.close();
+ Fawe.debug("Done!");
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void run() {
+ db.forEach(new Consumer>() {
+ @Override
+ public void accept(Map.Entry entry) {
+ byte[] key = entry.getKey();
+ if (key.length != 10) {
+ return;
+ }
+// byte[] value = entry.getValue();
+ }
+ });
+ // TODO
+ }
+}
diff --git a/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/MCAFile2LevelDB.java b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/MCAFile2LevelDB.java
new file mode 100644
index 00000000..48b23c76
--- /dev/null
+++ b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/MCAFile2LevelDB.java
@@ -0,0 +1,462 @@
+package com.boydti.fawe.nukkit.core.converter;
+
+import com.boydti.fawe.Fawe;
+import com.boydti.fawe.config.BBC;
+import com.boydti.fawe.jnbt.anvil.MCAChunk;
+import com.boydti.fawe.jnbt.anvil.MCAFile;
+import com.boydti.fawe.jnbt.anvil.MCAFilter;
+import com.boydti.fawe.jnbt.anvil.filters.DelegateMCAFilter;
+import com.boydti.fawe.jnbt.anvil.filters.RemapFilter;
+import com.boydti.fawe.object.RunnableVal;
+import com.boydti.fawe.object.clipboard.ClipboardRemapper;
+import com.boydti.fawe.object.collection.ByteStore;
+import com.boydti.fawe.object.io.LittleEndianOutputStream;
+import com.boydti.fawe.object.number.MutableLong;
+import com.boydti.fawe.util.ReflectionUtils;
+import com.boydti.fawe.util.StringMan;
+import com.sk89q.jnbt.ByteTag;
+import com.sk89q.jnbt.CompoundTag;
+import com.sk89q.jnbt.IntTag;
+import com.sk89q.jnbt.LongTag;
+import com.sk89q.jnbt.NBTInputStream;
+import com.sk89q.jnbt.NBTOutputStream;
+import com.sk89q.jnbt.NamedTag;
+import com.sk89q.jnbt.ShortTag;
+import com.sk89q.jnbt.StringTag;
+import com.sk89q.worldedit.blocks.BaseBlock;
+import com.sk89q.worldedit.world.registry.BundledBlockData;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.DataOutput;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.LongAdder;
+import java.util.zip.GZIPInputStream;
+import org.iq80.leveldb.DB;
+import org.iq80.leveldb.Options;
+import org.iq80.leveldb.impl.Iq80DBFactory;
+
+public class MCAFile2LevelDB implements Closeable {
+
+ private final ByteStore bufFinalizedState = new ByteStore(4);
+ private final ByteStore keyStore9 = new ByteStore(9);
+ private final ByteStore keyStore10 = new ByteStore(10);
+ private final ByteStore bufData2D = new ByteStore(512 + 256);
+ private final ByteStore bufSubChunkPrefix = new ByteStore(1 + 4096 + 2058 + 2048 + 2048);
+
+ private final byte[] VERSION = new byte[] { 4 };
+ private final byte[] COMPLETE_STATE = new byte[] { 2, 0, 0, 0 };
+
+ private final DB db;
+ private final ClipboardRemapper remapper;
+ private final ForkJoinPool pool;
+ private final File folder;
+ private boolean closed;
+ private LongAdder submitted = new LongAdder();
+
+ public MCAFile2LevelDB(File folder) {
+ try {
+ this.folder = folder;
+ if (!folder.exists()) {
+ folder.mkdirs();
+ }
+ String worldName = folder.getName();
+ try (PrintStream out = new PrintStream(new FileOutputStream(new File(folder, "levelname.txt")))) {
+ out.print(worldName);
+ }
+ this.pool = new ForkJoinPool();
+ this.remapper = new ClipboardRemapper(ClipboardRemapper.RemapPlatform.PC, ClipboardRemapper.RemapPlatform.PE);
+ BundledBlockData.getInstance().loadFromResource();
+ this.db = Iq80DBFactory.factory.open(new File(folder, "db"),
+ new Options()
+ .createIfMissing(true)
+ .verifyChecksums(false)
+ .blockSize(262144) // 256K
+ .cacheSize(8388608) // 8MB
+ .writeBufferSize(134217728) // >=128MB
+ );
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public MCAFilter toFilter() {
+ RemapFilter filter = new RemapFilter(ClipboardRemapper.RemapPlatform.PC, ClipboardRemapper.RemapPlatform.PE);
+ DelegateMCAFilter delegate = new DelegateMCAFilter(filter) {
+ @Override
+ public void finishFile(MCAFile file, MutableLong cache) {
+ file.forEachChunk(new RunnableVal() {
+ @Override
+ public void run(MCAChunk value) {
+ try {
+ write(value);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ file.clear();
+ }
+ };
+ return delegate;
+ }
+
+ @Override
+ public void close() {
+ try {
+ if (closed == (closed = true)) return;
+ Fawe.debug("Collecting threads");
+ pool.shutdown();
+ pool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
+ Fawe.debug("Closing");
+ db.close();
+ Fawe.debug("Done! (but still compacting)");
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void compact() {
+ // Since the library doesn't support it, only way to flush the cache is to loop over everything
+ try (DB newDb = Iq80DBFactory.factory.open(new File(folder, "db"), new Options()
+ .verifyChecksums(false)
+ .blockSize(262144) // 256K
+ .cacheSize(8388608) // 8MB
+ .writeBufferSize(134217728) // >=128MB
+ )) {
+ newDb.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ Fawe.debug("Done compacting!");
+ }
+
+ public void copyLevelDat(File in) throws IOException {
+ File levelDat = new File(folder, "level.dat");
+ try (NBTInputStream nis = new NBTInputStream(new GZIPInputStream(new FileInputStream(in)))) {
+ if (!levelDat.exists()) {
+ levelDat.createNewFile();
+ }
+ NamedTag named = nis.readNamedTag();
+ com.sk89q.jnbt.CompoundTag tag = (CompoundTag) ((CompoundTag) (named.getTag())).getValue().get("Data");
+ Map map = ReflectionUtils.getMap(tag.getValue());
+
+ HashSet allowed = new HashSet<>(Arrays.asList(
+ "Difficulty", "GameType", "Generator", "LastPlayed", "RandomSeed", "StorageVersion", "Time", "commandsEnabled", "currentTick", "rainTime", "spawnMobs", "GameRules", "SpawnX", "SpawnY", "SpawnZ"
+ ));
+ Iterator> iterator = map.entrySet().iterator();
+ while (iterator.hasNext()) {
+ Map.Entry entry = iterator.next();
+ if (!allowed.contains(entry.getKey())) {
+ iterator.remove();
+ }
+ }
+ {
+ Map gameRules = ((CompoundTag) map.remove("GameRules")).getValue();
+ for (Map.Entry entry : gameRules.entrySet()) {
+ String key = entry.getKey().toLowerCase();
+ String value = ((StringTag) entry.getValue()).getValue();
+ if (StringMan.isEqualIgnoreCaseToAny(value, "true", "false")) {
+ map.put(key, new ByteTag((byte) (value.equals("true") ? 1 : 0)));
+ }
+ }
+ map.put("LevelName", new StringTag(folder.getName()));
+ map.put("StorageVersion", new IntTag(5));
+ Byte difficulty = tag.getByte("Difficulty");
+ map.put("Difficulty", new IntTag(difficulty == null ? 2 : difficulty));
+ String generatorName = tag.getString("generatorName");
+ map.put("Generator", new IntTag("flat".equalsIgnoreCase(generatorName) ? 2 : 1));
+ map.put("commandsEnabled", new ByteTag((byte) 1));
+ Long time = tag.getLong("Time");
+ map.put("CurrentTick", new LongTag(time == null ? 0L : time));
+ map.put("spawnMobs", new ByteTag((byte) 1));
+ }
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try (NBTOutputStream nos = new NBTOutputStream((DataOutput) new LittleEndianOutputStream(baos))) {
+ nos.writeNamedTag("Name", tag);
+ }
+ LittleEndianOutputStream leos = new LittleEndianOutputStream(new FileOutputStream(levelDat));
+ leos.writeInt(5);
+ leos.writeInt(baos.toByteArray().length);
+ leos.write(baos.toByteArray());
+ leos.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void write(MCAChunk chunk) throws IOException {
+ submitted.add(1);
+ if ((submitted.longValue() & 127) == 0) {
+ pool.awaitQuiescence(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
+ long queued = pool.getQueuedTaskCount() + pool.getQueuedSubmissionCount();
+ if (queued > 127) {
+ System.gc();
+ while (queued > 64) {
+ try {
+ Thread.sleep(5);
+ queued = pool.getQueuedTaskCount() + pool.getQueuedSubmissionCount();
+ } catch (InterruptedException e) {
+ break;
+ }
+ }
+ }
+ }
+ pool.submit(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ update(getKey(chunk, Tag.Version), VERSION);
+ update(getKey(chunk, Tag.FinalizedState), COMPLETE_STATE);
+
+ ByteBuffer data2d = ByteBuffer.wrap(bufData2D.get());
+ int[] heightMap = chunk.getHeightMapArray();
+ for (int i = 0; i < heightMap.length; i++) {
+ data2d.putShort((short) heightMap[i]);
+ }
+ if (chunk.biomes != null) {
+ System.arraycopy(chunk.biomes, 0, data2d.array(), 512, 256);
+ }
+ update(getKey(chunk, Tag.Data2D), data2d.array());
+
+ if (!chunk.tiles.isEmpty()) {
+ List tiles = new ArrayList<>();
+ for (Map.Entry entry : chunk.getTiles().entrySet()) {
+ CompoundTag tag = entry.getValue();
+ tiles.add(transform(tag));
+ }
+ update(getKey(chunk, Tag.BlockEntity), write(tiles));
+ }
+
+ if (!chunk.entities.isEmpty()) {
+ List entities = new ArrayList<>();
+ for (com.sk89q.jnbt.CompoundTag tag : chunk.getEntities()) {
+ entities.add(transform(tag));
+ }
+ update(getKey(chunk, Tag.Entity), write(entities));
+ }
+
+ int maxLayer = chunk.ids.length - 1;
+ while (maxLayer >= 0 && chunk.ids[maxLayer] == null) maxLayer--;
+ if (maxLayer >= 0) {
+ byte[] key = getSectionKey(chunk, 0);
+ for (int layer = 0; layer <= maxLayer; layer++) {
+ // Set layer
+ key[9] = (byte) layer;
+ byte[] value = bufSubChunkPrefix.get();
+ byte[] ids = chunk.ids[layer];
+ if (ids == null) {
+ Arrays.fill(value, (byte) 0);
+ } else {
+ byte[] data = chunk.data[layer];
+ byte[] skyLight = chunk.skyLight[layer];
+ byte[] blockLight = chunk.blockLight[layer];
+
+ copySection(ids, value, 1);
+ copySection(data, value, 1 + 4096);
+ copySection(skyLight, value, 1 + 4096 + 2048);
+ copySection(blockLight, value, 1 + 4096 + 2048 + 2048);
+ }
+ update(key, value);
+ }
+ }
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
+
+ private void update(byte[] key, byte[] value) {
+ db.put(key.clone(), value.clone());
+ }
+
+ private void copySection(byte[] src, byte[] dest, int destPos) {
+ int len = src.length;
+ switch (src.length) {
+ case 4096: {
+ int index = 0;
+ int i1, i2, i3;
+ for (int y = 0; y < 16; y++) {
+ i1 = y;
+ for (int z = 0; z < 16; z++) {
+ i2 = i1 + (z << 4);
+ for (int x = 0; x < 16; x++) {
+ i3 = i2 + (x << 8);
+ dest[destPos + i3] = src[index];
+ index++;
+ }
+ }
+ }
+ break;
+ }
+ case 2048: {
+ int index = 0;
+ int i1, i2, i3, i4;
+ for (int x = 0; x < 16;) {
+ {
+ i1 = x;
+ for (int z = 0; z < 16; z++) {
+ i2 = i1 + (z << 4);
+ for (int y = 0; y < 16; y += 2) {
+ i3 = i2 + (y << 8);
+ i4 = i2 + ((y + 1) << 8);
+ byte newVal = (byte) ((src[i3 >> 1] & 0xF) + ((src[i4 >> 1] & 0xF) << 4));
+ dest[destPos + index] = newVal;
+ index++;
+ }
+ }
+ }
+ x++;
+ {
+ i1 = x;
+ for (int z = 0; z < 16; z++) {
+ i2 = i1 + (z << 4);
+ for (int y = 0; y < 16; y += 2) {
+ i3 = i2 + (y << 8);
+ i4 = i2 + ((y + 1) << 8);
+ byte newVal = (byte) (((src[i3 >> 1] & 0xF0) >> 4) + ((src[i4 >> 1] & 0xF0)));
+ dest[destPos + index] = newVal;
+ index++;
+ }
+ }
+ }
+ x++;
+
+ }
+ break;
+ }
+ default:
+ System.arraycopy(src, 0, dest, destPos, len);
+ }
+ }
+
+ private enum Tag {
+ Data2D(45),
+ @Deprecated Data2DLegacy(46),
+ SubChunkPrefix(47),
+ @Deprecated LegacyTerrain(48),
+ BlockEntity(49),
+ Entity(50),
+ PendingTicks(51),
+ BlockExtraData(52),
+ BiomeState(53),
+ FinalizedState(54),
+ Version(118),
+
+ ;
+ public final byte value;
+
+ Tag(int value) {
+ this.value = (byte) value;
+ }
+
+ public byte[] fill(int chunkX, int chunkZ, byte[] key) {
+ key[0] = (byte) (chunkX & 255);
+ key[1] = (byte) (chunkX >>> 8 & 255);
+ key[2] = (byte) (chunkX >>> 16 & 255);
+ key[3] = (byte) (chunkX >>> 24 & 255);
+ key[4] = (byte) (chunkZ & 255);
+ key[5] = (byte) (chunkZ >>> 8 & 255);
+ key[6] = (byte) (chunkZ >>> 16 & 255);
+ key[7] = (byte) (chunkZ >>> 24 & 255);
+ key[8] = value;
+ return key;
+ }
+ }
+
+ private byte[] write(Collection tags) throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ NBTOutputStream nos = new NBTOutputStream(baos);
+ nos.setLittleEndian();
+ for (com.sk89q.jnbt.Tag tag : tags) {
+ nos.writeNamedTag("", tag);
+ }
+ nos.close();
+ return baos.toByteArray();
+ }
+
+ private String convertId(String input) {
+ input = input.replace("minecraft:", "");
+ StringBuilder result = new StringBuilder();
+ boolean toUpper = false;
+ for (int i = 0; i < input.length(); i++) {
+ char c = input.charAt(i);
+ if (i == 0) toUpper = true;
+ if (c == '_') {
+ toUpper = true;
+ } else {
+ result.append(toUpper ? Character.toUpperCase(c) : c);
+ toUpper = false;
+ }
+ }
+ return result.toString();
+ }
+
+ private CompoundTag transform(CompoundTag tag) {
+ try {
+ String id = tag.getString("id");
+ if (id != null) {
+ Map map = ReflectionUtils.getMap(tag.getValue());
+ id = convertId(id);
+ map.put("id", new StringTag(id));
+ { // Convert items
+ com.sk89q.jnbt.ListTag items = tag.getListTag("Items");
+ for (CompoundTag item : (List) (List) items.getValue()) {
+ String itemId = item.getString("id");
+ BaseBlock state = BundledBlockData.getInstance().findByState(itemId);
+ if (state != null) {
+ int legacy = state.getId();
+ ReflectionUtils.getMap(item.getValue()).put("id", new ShortTag((short) legacy));
+ }
+ }
+ }
+ switch (id) {
+ case "MobSpawner": {
+ map.clear();
+ break;
+ }
+ case "Sign": {
+ for (int line = 1; line <= 4; line++) {
+ String key = "Text" + line;
+ String text = tag.getString(key);
+ if (text != null && text.startsWith("{")) {
+ map.put(key, new StringTag(BBC.jsonToString(text)));
+ }
+
+ }
+ }
+ }
+ }
+ } catch (Throwable e) {
+ Fawe.debug("Error converting tag: " + tag);
+ e.printStackTrace();
+ }
+ return tag;
+ }
+
+ private byte[] getSectionKey(MCAChunk chunk, int layer) {
+ byte[] key = Tag.SubChunkPrefix.fill(chunk.getX(), chunk.getZ(), keyStore10.get());
+ key[9] = (byte) layer;
+ return key;
+ }
+
+ private byte[] getKey(MCAChunk chunk, Tag tag) {
+
+ return tag.fill(chunk.getX(), chunk.getZ(), keyStore9.get());
+ }
+}
diff --git a/nukkit/src/main/java/com/boydti/fawe/nukkit/optimization/queue/NukkitQueue.java b/nukkit/src/main/java/com/boydti/fawe/nukkit/optimization/queue/NukkitQueue.java
index 1fbbe319..e95aedd7 100644
--- a/nukkit/src/main/java/com/boydti/fawe/nukkit/optimization/queue/NukkitQueue.java
+++ b/nukkit/src/main/java/com/boydti/fawe/nukkit/optimization/queue/NukkitQueue.java
@@ -130,6 +130,9 @@ public class NukkitQueue extends NMSMappedFaweQueueBUMaDxy-;mc3YJ?->>mNtlMcN+x9y
z6%rZYOIl_%N+K+@D3+8;nOIq_v24tdHrHC~p8hz$v1d>B*>j(J&wbtf!NuR0JNKU7
zIrn?Mzn63FAf=Q=CXq>x@$DkNw+-n*ik)|_#OXN)gINLSVN4`DiTWUcAiGFsG6`66
z1yDqhpH0Ao6`*cXs(}ii6F3s|WXw6h4@rHUUFsGv2ABkl2hImZ12sS;Fih;y4YUFE
zzn$bK~%)p9Wk5Oc%qKBc}l0h6zFCFeo>AkPy`0?<;{i;9j8ChnJaT_8i*Oja1xy8=1x_`H6k~&W<}^W26|fw*
zH2`*WYz9I7>l1-D<=-IW%%I*}D?+$1rC$bY5778M16&bA!}&Bci9xyRfLDVuPJx^V
z{1{X)F6f|>=?h>k@LEX5M;tff_Q{0=L8j$KU{%O(?Fm6TVk8KHW<_9pfYbBtSq=m2
zS5I0v`Xb=O
z-+EG5&||_vqL!Qb>0_mYEQXe2pRgP;KXNv3PfTENig?AK^9J_=Uje@Zhk>7fL%=)o
z+$D)Z+#RtT@HucsjGk#Xun>3!o!IGb&3IhiUfaz81Q`qRDm~6tlI2lgX)y*B=cIfk
z@I8G)!ATSwm;t<3DimSSH{@Du1uu8C_j=W(!1z6OSXiu|ULCP=7h*5)HJ|iIO)&_v
zT)aL}=#yf$@cNa{iuKni7UU-hZmMQyCdmwsJ4rjRqF8^N95JheUhb?e*Kq^5G$qDw
z1E!%%S{(ZuM;0^=_#p*vz+N%_zEX}(+#qOH3XK0TaIL%nRw;@UCkO(#B;m$?8Mp#y
z#bZV+N)}`R2Z0^vTFf@Ny7PXY6IwS~{Di8Q4ZwHk!d_Q>cZvnYfd!T0Esp~x1LvZP
zvZ8k08iA964mbZftXNQ3dQN(S*aBSOA^EAR-g
zp1xVMqHjPFA;_&%pyTr^2|uyZ0W<*b10Ml<;_iqAp?9Nm%Htv!{2{V7tpIkRlS{E>
zK|$yhzze|bF@eRAX<|X8q#9Uc#kWnpuz~
zGkzV>ma#a&`B}0c$2u0|S7sA#(XLxrD;~a;R!0orVd36!3+Ay9pV-L&$elRpb@~^^cGC{Sv!J|Wx%Fl?I#_AYSC3_qhm@4K1GMuyZVa-
zC6(!1C5x@!aI_6PgRbek(;#&12hMSTo|3VkB*+86QwE;76J7qYT0#}O4B4RxxDYpc
z%9K);Qi{CGim_g)lv<_C$RVZF1$pgTN~!Gz+C89@n&7ryPEJ=`*-j1cA$lXWU)dv0
z(bj^X-5Q_m?nWnu_PXs?QXpu8gcVGj`>+YP1$`D!p^O54#@iOC4Zv;aIw1Fw00_ER
z;KHx`tCU)zl**O3?>8RWUM}sH-A4+HAE(PwO5LrL`p1Cr|5QrNa(UmCWwd)#DOKmE
zpE(ETDh`rEAH1=^5F?oe>~z`3T`t>g0j@^ZeOi7x8i&wvzCKJMnI_5;8_;#8O=Ud8
zP8aQdfKIx%VqlwUoEF>hve=q9cw@aRwjE{uT@^ZaJVFfI3>=VvTB9%=V~6?0i3Od9
z-ilgdpsgn*DjNH|MqD6h9`GW%(8`Z=0e6XKYm<SFE`NEK6*>Rt)vY=6c9947F!cVQrm+a=etRX
z1Pwtw)oCV9vE7C~e7lA8Y+()b)Wpfa7lw?#8lBv3A>p4a!C{+f5Q91!eV(U@#dZ-o
zX`afjdYFbFOW4U0LyPSkdQTa#(mDX9SkMr3nMj?1=W3M2_G{Al6%XSqXf(P&%ESR1
zUrJ1;nE@asAgC5y8aT$l^Yp~6^8gv51?~X$oU7fZR9a7H3
zB?K)pVEj(>MuoKl)w%M+g8ZH;a*)0y+Zo#_lLa*a$9XjVPGLH~Wb@iP1T}gbfO>SP
zNP7l3{#ejr58;95gk~PeSdD)cG#Guj-ShNc&2SXGWpoWXXKQD3*_$|R&=`dNZsJDZ
zB>5RYiT;-?v|BRs1>=Y*^fsIYz;D3c!sQPGzlwvlHG`4hGh&0$`QDMhF+wq0(PtxP
l&Q*XAlu5)8l<6Vr{{iqu_R|~%0HXi^002ovPDHLkV1fb(yz&45
literal 0
HcmV?d00001
diff --git a/settings.gradle b/settings.gradle
index 6e956331..5f4acd2d 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,3 +1,3 @@
rootProject.name = 'FastAsyncWorldEdit'
-include 'core', 'bukkit' , 'favs', 'nukkit', 'sponge', 'forge1710', 'forge189', 'forge194', 'forge110', 'forge111'
\ No newline at end of file
+include 'core', 'bukkit', 'favs', 'nukkit', 'forge1710', 'forge189', 'forge194', 'forge110', 'forge111', 'sponge', 'forge112'
\ No newline at end of file
diff --git a/sponge/src/main/java/com/boydti/fawe/sponge/FaweSponge.java b/sponge/src/main/java/com/boydti/fawe/sponge/FaweSponge.java
index a007812c..6882dca9 100644
--- a/sponge/src/main/java/com/boydti/fawe/sponge/FaweSponge.java
+++ b/sponge/src/main/java/com/boydti/fawe/sponge/FaweSponge.java
@@ -10,6 +10,7 @@ import com.boydti.fawe.object.FaweQueue;
import com.boydti.fawe.regions.FaweMaskManager;
import com.boydti.fawe.util.MainUtil;
import com.boydti.fawe.util.TaskManager;
+import com.sk89q.worldedit.sponge.chat.SpongeChatManager;
import com.sk89q.worldedit.world.World;
import java.io.File;
import java.util.ArrayList;
@@ -37,6 +38,7 @@ public class FaweSponge implements IFawe {
Fawe.set(this);
Fawe.setupInjector();
com.sk89q.worldedit.sponge.SpongePlayer.inject();
+ Fawe.get().setChatManager(new SpongeChatManager());
} catch (final Throwable e) {
MainUtil.handleError(e);
}
diff --git a/sponge/src/main/java/com/sk89q/worldedit/sponge/chat/SpongeChatManager.java b/sponge/src/main/java/com/sk89q/worldedit/sponge/chat/SpongeChatManager.java
new file mode 100644
index 00000000..d343f637
--- /dev/null
+++ b/sponge/src/main/java/com/sk89q/worldedit/sponge/chat/SpongeChatManager.java
@@ -0,0 +1,167 @@
+package com.sk89q.worldedit.sponge.chat;
+
+import com.boydti.fawe.config.BBC;
+import com.boydti.fawe.object.FawePlayer;
+import com.boydti.fawe.sponge.SpongePlayer;
+import com.boydti.fawe.util.chat.ChatManager;
+import com.boydti.fawe.util.chat.Message;
+import com.boydti.fawe.wrappers.FakePlayer;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.List;
+import org.spongepowered.api.text.Text;
+import org.spongepowered.api.text.action.TextActions;
+import org.spongepowered.api.text.format.TextColor;
+import org.spongepowered.api.text.format.TextColors;
+import org.spongepowered.api.text.format.TextStyle;
+import org.spongepowered.api.text.format.TextStyles;
+import org.spongepowered.api.text.serializer.TextSerializers;
+
+public class SpongeChatManager implements ChatManager {
+
+ @Override
+ public Text.Builder builder() {
+ return Text.builder();
+ }
+
+ @Override
+ public void color(Message message, String color) {
+ TextColor tc = null;
+ TextStyle ts = null;
+ switch (color.charAt(1)) {
+ case 'a':
+ tc = TextColors.GREEN;
+ break;
+ case 'b':
+ tc = TextColors.AQUA;
+ break;
+ case 'c':
+ tc = TextColors.RED;
+ break;
+ case 'd':
+ tc = TextColors.LIGHT_PURPLE;
+ break;
+ case 'e':
+ tc = TextColors.YELLOW;
+ break;
+ case 'f':
+ tc = TextColors.WHITE;
+ break;
+ case '1':
+ tc = TextColors.DARK_BLUE;
+ break;
+ case '2':
+ tc = TextColors.DARK_GREEN;
+ break;
+ case '3':
+ tc = TextColors.DARK_AQUA;
+ break;
+ case '4':
+ tc = TextColors.DARK_RED;
+ break;
+ case '5':
+ tc = TextColors.DARK_PURPLE;
+ break;
+ case '6':
+ tc = TextColors.GOLD;
+ break;
+ case '7':
+ tc = TextColors.GRAY;
+ break;
+ case '8':
+ tc = TextColors.DARK_GRAY;
+ break;
+ case '9':
+ tc = TextColors.BLUE;
+ break;
+ case '0':
+ tc = TextColors.BLACK;
+ break;
+ case 'k':
+ ts = TextStyles.OBFUSCATED;
+ break;
+ case 'l':
+ ts = TextStyles.BOLD;
+ break;
+ case 'm':
+ ts = TextStyles.UNDERLINE;
+ break;
+ case 'n':
+ ts = TextStyles.STRIKETHROUGH;
+ break;
+ case 'o':
+ ts = TextStyles.ITALIC;
+ break;
+ case 'r':
+ tc = TextColors.RESET;
+ break;
+ }
+ if (tc != null) {
+ apply(message, getChild(message).color(tc));
+ }
+ if (ts != null) {
+ apply(message, getChild(message).style(ts));
+ }
+ }
+
+ public Text.Builder getChild(Message m) {
+ Text.Builder builder = m.$(this);
+ List children = builder.getChildren();
+ Text last = children.get(children.size() - 1);
+ builder.remove(last);
+ return Text.builder().append(last);
+ }
+
+ public void apply(Message m, Text.Builder builder) {
+ m.$(this).append(builder.build());
+ }
+
+ @Override
+ public void tooltip(Message message, Message... tooltips) {
+ Text.Builder builder = Text.builder();
+ boolean lb = false;
+ for (Message tooltip : tooltips) {
+ if (lb) {
+ builder.append(Text.of("\n"));
+ }
+ builder.append(tooltip.$(this).build());
+ lb = true;
+ }
+ apply(message, getChild(message).onHover(TextActions.showText(builder.toText())));
+ }
+
+ @Override
+ public void command(Message message, String command) {
+ apply(message, getChild(message).onClick(TextActions.runCommand(command)));
+ }
+
+ @Override
+ public void text(Message message, String text) {
+ message.$(this).append(TextSerializers.LEGACY_FORMATTING_CODE.deserialize(BBC.color(text)));
+ }
+
+ @Override
+ public void send(Message Message, FawePlayer player) {
+ if (player == FakePlayer.getConsole().toFawePlayer()) {
+ player.sendMessage(Message.$(this).build().toPlain());
+ } else {
+ ((SpongePlayer) player).parent.sendMessage(Message.$(this).build());
+ }
+ }
+
+ @Override
+ public void suggest(Message Message, String command) {
+ apply(Message, getChild(Message).onClick(TextActions.suggestCommand(command)));
+ }
+
+ @Override
+ public void link(Message message, String url) {
+ try {
+ if (!url.isEmpty()) {
+ apply(message, getChild(message).onClick(TextActions.openUrl(new URL(url))));
+ }
+ } catch (MalformedURLException e) {
+ e.printStackTrace();
+ }
+ }
+}