Start work on interactive commands (//help, //biomelist, //pos1/2)
Added clipboard and world remapping (between mcpe/pe)
//schematic remap
//anvil remapall
Added anvil -> leveldb converter (run Nukkit jar or /anvil2leveldb)
Added safe zstd decompressor
Angle mask fixes
Fix paste air issues with fawe format (which no-one uses anyway)
Fix cfi file:// with an absolute path
FIx schematic format and addBlocks
Update forge to 1.12
Fixes #663
This commit is contained in:
Jesse Boyd 2017-07-28 15:12:41 +10:00
parent 590b620d9d
commit 8dfcb0f914
No known key found for this signature in database
GPG Key ID: 59F1DE6293AF6E1F
120 changed files with 12385 additions and 549 deletions

View File

@ -1,15 +1,14 @@
# FastAsyncWorldEdit
FAWE is an addon for WorldEdit that has huge speed and memory improvements as well as a few extra features.
FAWE is a fork of WorldEdit that has huge speed and memory improvements and considerably more features
## Spigot page
https://www.spigotmc.org/resources/13932/
It is available for Bukkit, Forge, Sponge and Nukkit.
## IRC
Meet us on `irc.esper.net` in the `#IntellectualCrafters` channel or use the [webchat to join through browser](http://webchat.esper.net/?nick=&channels=IntellectualCrafters).
## Chat
Meet us on [`Discord`](https://discord.gg/ngZCzbU) or [`irc.esper.net #IntellectualCrafters`](http://webchat.esper.net/?nick=&channels=IntellectualCrafters).
## Releases
You can find the most recent stable releases on GitHub
https://github.com/boy0001/FastAsyncWorldedit/releases
https://github.com/boy0001/FastAsyncWorldedit/releases/latest
## Building
FAWE uses gradle to build
@ -23,4 +22,4 @@ $ gradlew build
Have an idea for an optimization, or a cool feature?
- I'll accept most PR's
- Let me know what you've tested / what may need further testing
- If you need any help, create a ticket or discuss on IRC
- If you need any help, create a ticket or discuss on Discord

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,7 @@ package com.boydti.fawe.bukkit;
import com.boydti.fawe.Fawe;
import com.boydti.fawe.IFawe;
import com.boydti.fawe.bukkit.chat.BukkitChatManager;
import com.boydti.fawe.bukkit.regions.FactionsFeature;
import com.boydti.fawe.bukkit.regions.FactionsOneFeature;
import com.boydti.fawe.bukkit.regions.FactionsUUIDFeature;
@ -31,6 +32,7 @@ import com.boydti.fawe.util.MainUtil;
import com.boydti.fawe.util.ReflectionUtils;
import com.boydti.fawe.util.TaskManager;
import com.sk89q.bukkit.util.FallbackRegistrationListener;
import com.sk89q.worldedit.bukkit.BukkitPlayerBlockBag;
import com.sk89q.worldedit.bukkit.BukkitWorld;
import com.sk89q.worldedit.bukkit.EditSessionBlockChangeDelegate;
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
@ -78,6 +80,7 @@ public class FaweBukkit implements IFawe, Listener {
com.sk89q.worldedit.bukkit.BukkitPlayer.inject(); // Fixes
BukkitWorld.inject(); // Fixes
FallbackRegistrationListener.inject(); // Fixes
BukkitPlayerBlockBag.inject(); // features
try {
new BrushListener(plugin);
} catch (Throwable e) {
@ -95,6 +98,11 @@ public class FaweBukkit implements IFawe, Listener {
if (Bukkit.getVersion().contains("git-Paper") && Settings.IMP.EXPERIMENTAL.DYNAMIC_CHUNK_RENDERING) {
new RenderListener(plugin);
}
try {
Fawe.get().setChatManager(new BukkitChatManager());
} catch (Throwable ignore) {
ignore.printStackTrace();
}
} catch (final Throwable e) {
MainUtil.handleError(e);
Bukkit.getServer().shutdown();

View File

@ -0,0 +1,110 @@
package com.boydti.fawe.bukkit.chat;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Collection;
import org.apache.commons.lang.Validate;
/**
* Represents a wrapper around an array class of an arbitrary reference type,
* which properly implements "value" hash code and equality functions.
* <p>
* This class is intended for use as a key to a map.
* </p>
*
* @param <E> The type of elements in the array.
* @author Glen Husman
* @see java.util.Arrays
*/
public final class ArrayWrapper<E> {
/**
* Creates an array wrapper with some elements.
*
* @param elements The elements of the array.
*/
public ArrayWrapper(E... elements) {
setArray(elements);
}
private E[] _array;
/**
* Retrieves a reference to the wrapped array instance.
*
* @return The array wrapped by this instance.
*/
public E[] getArray() {
return _array;
}
/**
* Set this wrapper to wrap a new array instance.
*
* @param array The new wrapped array.
*/
public void setArray(E[] array) {
Validate.notNull(array, "The array must not be null.");
_array = array;
}
/**
* Determines if this object has a value equivalent to another object.
*
* @see java.util.Arrays#equals(Object[], Object[])
*/
@SuppressWarnings("rawtypes")
@Override
public boolean equals(Object other) {
if (!(other instanceof ArrayWrapper)) {
return false;
}
return Arrays.equals(_array, ((ArrayWrapper) other)._array);
}
/**
* Gets the hash code represented by this objects value.
*
* @return This object's hash code.
* @see java.util.Arrays#hashCode(Object[])
*/
@Override
public int hashCode() {
return Arrays.hashCode(_array);
}
/**
* Converts an iterable element collection to an array of elements.
* The iteration order of the specified object will be used as the array element order.
*
* @param list The iterable of objects which will be converted to an array.
* @param c The type of the elements of the array.
* @return An array of elements in the specified iterable.
*/
@SuppressWarnings("unchecked")
public static <T> T[] toArray(Iterable<? extends T> list, Class<T> c) {
int size = -1;
if (list instanceof Collection<?>) {
@SuppressWarnings("rawtypes")
Collection coll = (Collection) list;
size = coll.size();
}
if (size < 0) {
size = 0;
// Ugly hack: Count it ourselves
for (@SuppressWarnings("unused") T element : list) {
size++;
}
}
T[] result = (T[]) Array.newInstance(c, size);
int i = 0;
for (T element : list) { // Assumes iteration order is consistent
result[i++] = element; // Assign array element at index THEN increment counter
}
return result;
}
}

View File

@ -0,0 +1,64 @@
package com.boydti.fawe.bukkit.chat;
import com.boydti.fawe.bukkit.BukkitPlayer;
import com.boydti.fawe.config.BBC;
import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.util.chat.ChatManager;
import com.boydti.fawe.util.chat.Message;
import com.boydti.fawe.wrappers.FakePlayer;
import java.util.ArrayList;
import java.util.List;
import org.bukkit.ChatColor;
public class BukkitChatManager implements ChatManager<FancyMessage> {
@Override
public FancyMessage builder() {
return new FancyMessage("");
}
@Override
public void color(Message message, String color) {
message.$(this).color(ChatColor.getByChar(BBC.color(color).substring(1)));
}
@Override
public void tooltip(Message message, Message... tooltips) {
List<FancyMessage> lines = new ArrayList<>();
for (Message tooltip : tooltips) {
lines.add(tooltip.$(this));
}
message.$(this).formattedTooltip(lines);
}
@Override
public void command(Message message, String command) {
message.$(this).command(command);
}
@Override
public void text(Message message, String text) {
message.$(this).color(BBC.color(text));
}
@Override
public void send(Message Message, FawePlayer player) {
if (player == FakePlayer.getConsole().toFawePlayer()) {
player.sendMessage(Message.$(this).toOldMessageFormat());
} else {
Message.$(this).send(((BukkitPlayer) player).parent);
}
}
@Override
public void suggest(Message Message, String command) {
Message.$(this).suggest(command);
}
@Override
public void link(Message Message, String url) {
Message.$(this).link(url);
}
}

View File

@ -0,0 +1,955 @@
package com.boydti.fawe.bukkit.chat;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.logging.Level;
import org.bukkit.Achievement;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.Statistic;
import org.bukkit.Statistic.Type;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.configuration.serialization.ConfigurationSerialization;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import static com.boydti.fawe.bukkit.chat.TextualComponent.rawText;
/**
* Represents a formattable message. Such messages can use elements such as colors, formatting codes, hover and click data, and other features provided by the vanilla Minecraft <a href="http://minecraft.gamepedia.com/Tellraw#Raw_JSON_Text">JSON message formatter</a>.
* This class allows plugins to emulate the functionality of the vanilla Minecraft <a href="http://minecraft.gamepedia.com/Commands#tellraw">tellraw command</a>.
* <p>
* This class follows the builder pattern, allowing for method chaining.
* It is set up such that invocations of property-setting methods will affect the current editing component,
* and a call to {@link #then()} or {@link #then(String)} will append a new editing component to the end of the message,
* optionally initializing it with text. Further property-setting method calls will affect that editing component.
* </p>
*/
public class FancyMessage implements JsonRepresentedObject, Cloneable, Iterable<MessagePart>, ConfigurationSerializable {
static {
ConfigurationSerialization.registerClass(FancyMessage.class);
}
private List<MessagePart> messageParts;
private int index = 0;
private String jsonString;
private boolean dirty;
private static Constructor<?> nmsPacketPlayOutChatConstructor;
@Override
public FancyMessage clone() throws CloneNotSupportedException {
FancyMessage instance = (FancyMessage) super.clone();
instance.messageParts = new ArrayList<>(messageParts.size());
for (int i = 0; i < messageParts.size(); i++) {
instance.messageParts.add(i, messageParts.get(i).clone());
}
instance.index = index;
instance.dirty = false;
instance.jsonString = null;
return instance;
}
/**
* Creates a JSON message with text.
*
* @param firstPartText The existing text in the message.
*/
public FancyMessage(final String firstPartText) {
this(rawText(firstPartText));
}
private FancyMessage(final TextualComponent firstPartText) {
messageParts = new ArrayList<>();
messageParts.add(new MessagePart(firstPartText));
index = messageParts.size();
jsonString = null;
dirty = false;
if (nmsPacketPlayOutChatConstructor == null) {
try {
nmsPacketPlayOutChatConstructor = Reflection.getNMSClass("PacketPlayOutChat").getDeclaredConstructor(Reflection.getNMSClass("IChatBaseComponent"));
nmsPacketPlayOutChatConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
Bukkit.getLogger().log(Level.SEVERE, "Could not find Minecraft method or constructor.", e);
} catch (SecurityException e) {
Bukkit.getLogger().log(Level.WARNING, "Could not access constructor.", e);
}
}
}
/**
* Creates a JSON message without text.
*/
public FancyMessage() {
this((TextualComponent) null);
}
/**
* Sets the text of the current editing component to a value.
*
* @param text The new text of the current editing component.
* @return This builder instance.
*/
public FancyMessage text(String text) {
MessagePart latest = latest();
latest.text = rawText(text);
dirty = true;
return this;
}
/**
* Sets the text of the current editing component to a value.
*
* @param text The new text of the current editing component.
* @return This builder instance.
*/
public FancyMessage text(TextualComponent text) {
MessagePart latest = latest();
latest.text = text;
dirty = true;
return this;
}
/**
*
* @param text Text with coloring
* @return This builder instance.
* @throws IllegalArgumentException If the specified {@code ChatColor} enumeration value is not a color (but a format value).
*/
public FancyMessage color(String text) {
index = messageParts.size();
boolean color = false;
ArrayDeque<ChatColor> colors = new ArrayDeque<>();
int last = 0;
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
if (color != (color = false)) {
ChatColor chatColor = ChatColor.getByChar(c);
if (chatColor != null) {
if (i - last > 1) {
append(text.substring(last, i - 1));
colors.forEach(this::color);
colors.clear();
}
colors.add(chatColor);
last = i + 1;
}
}
if (c == '\u00A7') {
color = true;
}
}
if (text.length() - last > 0) {
append(text.substring(last, text.length()));
colors.forEach(this::color);
}
index++;
return this;
}
/**
* Sets the color of the current editing component to a value.<br />
* Setting the color will clear current styles
*
* @param color The new color of the current editing component.
* @return This builder instance.
* @throws IllegalArgumentException If the specified {@code ChatColor} enumeration value is not a color (but a format value).
*/
public FancyMessage color(ChatColor color) {
if (!color.isColor()) {
if (color.isFormat()) {
return style(color);
}
if (color == ChatColor.RESET) {
color = ChatColor.WHITE;
}
} else {
latest().styles.clear();
}
latest().color = color;
dirty = true;
return this;
}
/**
* Sets the stylization of the current editing component.
*
* @param styles The array of styles to apply to the editing component.
* @return This builder instance.
* @throws IllegalArgumentException If any of the enumeration values in the array do not represent formatters.
*/
public FancyMessage style(ChatColor... styles) {
for (final ChatColor style : styles) {
if (!style.isFormat()) {
color(style);
}
}
latest().styles.addAll(Arrays.asList(styles));
dirty = true;
return this;
}
/**
* Set the behavior of the current editing component to instruct the client to open a file on the client side filesystem when the currently edited part of the {@code FancyMessage} is clicked.
*
* @param path The path of the file on the client filesystem.
* @return This builder instance.
*/
public FancyMessage file(final String path) {
onClick("open_file", path);
return this;
}
/**
* Set the behavior of the current editing component to instruct the client to open a webpage in the client's web browser when the currently edited part of the {@code FancyMessage} is clicked.
*
* @param url The URL of the page to open when the link is clicked.
* @return This builder instance.
*/
public FancyMessage link(final String url) {
onClick("open_url", url);
return this;
}
/**
* Set the behavior of the current editing component to instruct the client to replace the chat input box content with the specified string when the currently edited part of the {@code FancyMessage} is clicked.
* The client will not immediately send the command to the server to be executed unless the client player submits the command/chat message, usually with the enter key.
*
* @param command The text to display in the chat bar of the client.
* @return This builder instance.
*/
public FancyMessage suggest(final String command) {
onClick("suggest_command", command);
return this;
}
/**
* Set the behavior of the current editing component to instruct the client to append the chat input box content with the specified string when the currently edited part of the {@code FancyMessage} is SHIFT-CLICKED.
* The client will not immediately send the command to the server to be executed unless the client player submits the command/chat message, usually with the enter key.
*
* @param command The text to append to the chat bar of the client.
* @return This builder instance.
*/
public FancyMessage insert(final String command) {
onCurrent(m -> m.insertionData = command);
dirty = true;
return this;
}
/**
* Set the behavior of the current editing component to instruct the client to send the specified string to the server as a chat message when the currently edited part of the {@code FancyMessage} is clicked.
* The client <b>will</b> immediately send the command to the server to be executed when the editing component is clicked.
*
* @param command The text to display in the chat bar of the client.
* @return This builder instance.
*/
public FancyMessage command(final String command) {
onClick("run_command", command);
return this;
}
/**
* Set the behavior of the current editing component to display information about an achievement when the client hovers over the text.
* <p>Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.</p>
*
* @param name The name of the achievement to display, excluding the "achievement." prefix.
* @return This builder instance.
*/
public FancyMessage achievementTooltip(final String name) {
onHover("show_achievement", new JsonString("achievement." + name));
return this;
}
/**
* Set the behavior of the current editing component to display information about an achievement when the client hovers over the text.
* <p>Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.</p>
*
* @param which The achievement to display.
* @return This builder instance.
*/
public FancyMessage achievementTooltip(final Achievement which) {
try {
Object achievement = Reflection.getMethod(Reflection.getOBCClass("CraftStatistic"), "getNMSAchievement", Achievement.class).invoke(null, which);
return achievementTooltip((String) Reflection.getField(Reflection.getNMSClass("Achievement"), "name").get(achievement));
} catch (IllegalAccessException e) {
Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e);
return this;
} catch (IllegalArgumentException e) {
Bukkit.getLogger().log(Level.WARNING, "Argument could not be passed.", e);
return this;
} catch (InvocationTargetException e) {
Bukkit.getLogger().log(Level.WARNING, "A error has occurred during invoking of method.", e);
return this;
}
}
/**
* Set the behavior of the current editing component to display information about a parameterless statistic when the client hovers over the text.
* <p>Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.</p>
*
* @param which The statistic to display.
* @return This builder instance.
* @throws IllegalArgumentException If the statistic requires a parameter which was not supplied.
*/
public FancyMessage statisticTooltip(final Statistic which) {
Type type = which.getType();
if (type != Type.UNTYPED) {
throw new IllegalArgumentException("That statistic requires an additional " + type + " parameter!");
}
try {
Object statistic = Reflection.getMethod(Reflection.getOBCClass("CraftStatistic"), "getNMSStatistic", Statistic.class).invoke(null, which);
return achievementTooltip((String) Reflection.getField(Reflection.getNMSClass("Statistic"), "name").get(statistic));
} catch (IllegalAccessException e) {
Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e);
return this;
} catch (IllegalArgumentException e) {
Bukkit.getLogger().log(Level.WARNING, "Argument could not be passed.", e);
return this;
} catch (InvocationTargetException e) {
Bukkit.getLogger().log(Level.WARNING, "A error has occurred during invoking of method.", e);
return this;
}
}
/**
* Set the behavior of the current editing component to display information about a statistic parameter with a material when the client hovers over the text.
* <p>Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.</p>
*
* @param which The statistic to display.
* @param item The sole material parameter to the statistic.
* @return This builder instance.
* @throws IllegalArgumentException If the statistic requires a parameter which was not supplied, or was supplied a parameter that was not required.
*/
public FancyMessage statisticTooltip(final Statistic which, Material item) {
Type type = which.getType();
if (type == Type.UNTYPED) {
throw new IllegalArgumentException("That statistic needs no additional parameter!");
}
if ((type == Type.BLOCK && item.isBlock()) || type == Type.ENTITY) {
throw new IllegalArgumentException("Wrong parameter type for that statistic - needs " + type + "!");
}
try {
Object statistic = Reflection.getMethod(Reflection.getOBCClass("CraftStatistic"), "getMaterialStatistic", Statistic.class, Material.class).invoke(null, which, item);
return achievementTooltip((String) Reflection.getField(Reflection.getNMSClass("Statistic"), "name").get(statistic));
} catch (IllegalAccessException e) {
Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e);
return this;
} catch (IllegalArgumentException e) {
Bukkit.getLogger().log(Level.WARNING, "Argument could not be passed.", e);
return this;
} catch (InvocationTargetException e) {
Bukkit.getLogger().log(Level.WARNING, "A error has occurred during invoking of method.", e);
return this;
}
}
/**
* Set the behavior of the current editing component to display information about a statistic parameter with an entity type when the client hovers over the text.
* <p>Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.</p>
*
* @param which The statistic to display.
* @param entity The sole entity type parameter to the statistic.
* @return This builder instance.
* @throws IllegalArgumentException If the statistic requires a parameter which was not supplied, or was supplied a parameter that was not required.
*/
public FancyMessage statisticTooltip(final Statistic which, EntityType entity) {
Type type = which.getType();
if (type == Type.UNTYPED) {
throw new IllegalArgumentException("That statistic needs no additional parameter!");
}
if (type != Type.ENTITY) {
throw new IllegalArgumentException("Wrong parameter type for that statistic - needs " + type + "!");
}
try {
Object statistic = Reflection.getMethod(Reflection.getOBCClass("CraftStatistic"), "getEntityStatistic", Statistic.class, EntityType.class).invoke(null, which, entity);
return achievementTooltip((String) Reflection.getField(Reflection.getNMSClass("Statistic"), "name").get(statistic));
} catch (IllegalAccessException e) {
Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e);
return this;
} catch (IllegalArgumentException e) {
Bukkit.getLogger().log(Level.WARNING, "Argument could not be passed.", e);
return this;
} catch (InvocationTargetException e) {
Bukkit.getLogger().log(Level.WARNING, "A error has occurred during invoking of method.", e);
return this;
}
}
/**
* Set the behavior of the current editing component to display information about an item when the client hovers over the text.
* <p>Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.</p>
*
* @param itemJSON A string representing the JSON-serialized NBT data tag of an {@link org.bukkit.inventory.ItemStack}.
* @return This builder instance.
*/
public FancyMessage itemTooltip(final String itemJSON) {
onHover("show_item", new JsonString(itemJSON)); // Seems a bit hacky, considering we have a JSON object as a parameter
return this;
}
/**
* Set the behavior of the current editing component to display information about an item when the client hovers over the text.
* <p>Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.</p>
*
* @param itemStack The stack for which to display information.
* @return This builder instance.
*/
public FancyMessage itemTooltip(final ItemStack itemStack) {
try {
Object nmsItem = Reflection.getMethod(Reflection.getOBCClass("inventory.CraftItemStack"), "asNMSCopy", ItemStack.class).invoke(null, itemStack);
return itemTooltip(Reflection.getMethod(Reflection.getNMSClass("ItemStack"), "save", Reflection.getNMSClass("NBTTagCompound")).invoke(nmsItem, Reflection.getNMSClass("NBTTagCompound").newInstance()).toString());
} catch (Exception e) {
e.printStackTrace();
return this;
}
}
/**
* Set the behavior of the current editing component to display raw text when the client hovers over the text.
* <p>Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.</p>
*
* @param text The text, which supports newlines, which will be displayed to the client upon hovering.
* @return This builder instance.
*/
public FancyMessage tooltip(final String text) {
onHover("show_text", new JsonString(text));
return this;
}
/**
* Set the behavior of the current editing component to display raw text when the client hovers over the text.
* <p>Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.</p>
*
* @param lines The lines of text which will be displayed to the client upon hovering. The iteration order of this object will be the order in which the lines of the tooltip are created.
* @return This builder instance.
*/
public FancyMessage tooltip(final Iterable<String> lines) {
tooltip(com.boydti.fawe.bukkit.chat.ArrayWrapper.toArray(lines, String.class));
return this;
}
/**
* Set the behavior of the current editing component to display raw text when the client hovers over the text.
* <p>Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.</p>
*
* @param lines The lines of text which will be displayed to the client upon hovering.
* @return This builder instance.
*/
public FancyMessage tooltip(final String... lines) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < lines.length; i++) {
builder.append(lines[i]);
if (i != lines.length - 1) {
builder.append('\n');
}
}
tooltip(builder.toString());
return this;
}
/**
* Set the behavior of the current editing component to display formatted text when the client hovers over the text.
* <p>Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.</p>
*
* @param text The formatted text which will be displayed to the client upon hovering.
* @return This builder instance.
*/
public FancyMessage formattedTooltip(FancyMessage text) {
for (MessagePart component : text.messageParts) {
if (component.clickActionData != null && component.clickActionName != null) {
throw new IllegalArgumentException("The tooltip text cannot have click data.");
} else if (component.hoverActionData != null && component.hoverActionName != null) {
throw new IllegalArgumentException("The tooltip text cannot have a tooltip.");
}
}
onHover("show_text", text);
return this;
}
/**
* Set the behavior of the current editing component to display the specified lines of formatted text when the client hovers over the text.
* <p>Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.</p>
*
* @param lines The lines of formatted text which will be displayed to the client upon hovering.
* @return This builder instance.
*/
public FancyMessage formattedTooltip(FancyMessage... lines) {
if (lines.length < 1) {
onHover(null, null); // Clear tooltip
return this;
}
FancyMessage result = new FancyMessage();
result.messageParts.clear(); // Remove the one existing text component that exists by default, which destabilizes the object
for (int i = 0; i < lines.length; i++) {
try {
for (MessagePart component : lines[i]) {
if (component.clickActionData != null && component.clickActionName != null) {
throw new IllegalArgumentException("The tooltip text cannot have click data.");
} else if (component.hoverActionData != null && component.hoverActionName != null) {
throw new IllegalArgumentException("The tooltip text cannot have a tooltip.");
}
if (component.hasText()) {
result.messageParts.add(component.clone());
result.index = result.messageParts.size();
}
}
if (i != lines.length - 1) {
result.messageParts.add(new MessagePart(rawText("\n")));
result.index = result.messageParts.size();
}
} catch (CloneNotSupportedException e) {
Bukkit.getLogger().log(Level.WARNING, "Failed to clone object", e);
return this;
}
}
return formattedTooltip(result.messageParts.isEmpty() ? null : result); // Throws NPE if size is 0, intended
}
/**
* Set the behavior of the current editing component to display the specified lines of formatted text when the client hovers over the text.
* <p>Tooltips do not inherit display characteristics, such as color and styles, from the message component on which they are applied.</p>
*
* @param lines The lines of text which will be displayed to the client upon hovering. The iteration order of this object will be the order in which the lines of the tooltip are created.
* @return This builder instance.
*/
public FancyMessage formattedTooltip(final Iterable<FancyMessage> lines) {
return formattedTooltip(com.boydti.fawe.bukkit.chat.ArrayWrapper.toArray(lines, FancyMessage.class));
}
/**
* If the text is a translatable key, and it has replaceable values, this function can be used to set the replacements that will be used in the message.
*
* @param replacements The replacements, in order, that will be used in the language-specific message.
* @return This builder instance.
*/
public FancyMessage translationReplacements(final String... replacements) {
for (String str : replacements) {
latest().translationReplacements.add(new JsonString(str));
}
dirty = true;
return this;
}
/*
/**
* If the text is a translatable key, and it has replaceable values, this function can be used to set the replacements that will be used in the message.
* @param replacements The replacements, in order, that will be used in the language-specific message.
* @return This builder instance.
*/ /* ------------
public FancyMessage translationReplacements(final Iterable<? extends CharSequence> replacements){
for(CharSequence str : replacements){
latest().translationReplacements.add(new JsonString(str));
}
return this;
}
*/
/**
* If the text is a translatable key, and it has replaceable values, this function can be used to set the replacements that will be used in the message.
*
* @param replacements The replacements, in order, that will be used in the language-specific message.
* @return This builder instance.
*/
public FancyMessage translationReplacements(final FancyMessage... replacements) {
for (FancyMessage str : replacements) {
latest().translationReplacements.add(str);
}
dirty = true;
return this;
}
/**
* If the text is a translatable key, and it has replaceable values, this function can be used to set the replacements that will be used in the message.
*
* @param replacements The replacements, in order, that will be used in the language-specific message.
* @return This builder instance.
*/
public FancyMessage translationReplacements(final Iterable<FancyMessage> replacements) {
return translationReplacements(com.boydti.fawe.bukkit.chat.ArrayWrapper.toArray(replacements, FancyMessage.class));
}
/**
* Terminate construction of the current editing component, and begin construction of a new message component.
* After a successful call to this method, all setter methods will refer to a new message component, created as a result of the call to this method.
*
* @param text The text which will populate the new message component.
* @return This builder instance.
*/
public FancyMessage then(final String text) {
return then(rawText(text));
}
private FancyMessage append(final String text) {
if (!latest().hasText()) {
throw new IllegalStateException("previous message part has no text");
}
MessagePart latest = latest();
messageParts.add(new MessagePart(rawText(text)));
latest().color = latest.color;
latest().styles.addAll(latest.styles);
dirty = true;
return this;
}
/**
* Terminate construction of the current editing component, and begin construction of a new message component.
* After a successful call to this method, all setter methods will refer to a new message component, created as a result of the call to this method.
*
* @param text The text which will populate the new message component.
* @return This builder instance.
*/
public FancyMessage then(final TextualComponent text) {
if (!latest().hasText()) {
throw new IllegalStateException("previous message part has no text");
}
messageParts.add(new MessagePart(text));
index = messageParts.size();
dirty = true;
return this;
}
/**
* Terminate construction of the current editing component, and begin construction of a new message component.
* After a successful call to this method, all setter methods will refer to a new message component, created as a result of the call to this method.
*
* @return This builder instance.
*/
public FancyMessage then() {
if (!latest().hasText()) {
throw new IllegalStateException("previous message part has no text");
}
messageParts.add(new MessagePart());
index = messageParts.size();
dirty = true;
return this;
}
@Override
public void writeJson(JsonWriter writer) throws IOException {
if (messageParts.size() == 1) {
latest().writeJson(writer);
} else {
writer.beginObject().name("text").value("").name("extra").beginArray();
for (final MessagePart part : this) {
part.writeJson(writer);
}
writer.endArray().endObject();
}
}
/**
* Serialize this fancy message, converting it into syntactically-valid JSON using a {@link com.google.gson.stream.JsonWriter}.
* This JSON should be compatible with vanilla formatter commands such as {@code /tellraw}.
*
* @return The JSON string representing this object.
*/
public String toJSONString() {
if (!dirty && jsonString != null) {
return jsonString;
}
StringWriter string = new StringWriter();
JsonWriter json = new JsonWriter(string);
try {
writeJson(json);
json.close();
} catch (IOException e) {
throw new RuntimeException("invalid message");
}
jsonString = string.toString();
dirty = false;
return jsonString;
}
/**
* Sends this message to a player. The player will receive the fully-fledged formatted display of this message.
*
* @param player The player who will receive the message.
*/
public void send(Player player) {
send(player, toJSONString());
}
private void send(CommandSender sender, String jsonString) {
if (!(sender instanceof Player)) {
sender.sendMessage(toOldMessageFormat());
return;
}
Player player = (Player) sender;
try {
Object handle = Reflection.getHandle(player);
Object connection = Reflection.getField(handle.getClass(), "playerConnection").get(handle);
Reflection.getMethod(connection.getClass(), "sendPacket", Reflection.getNMSClass("Packet")).invoke(connection, createChatPacket(jsonString));
} catch (IllegalArgumentException e) {
Bukkit.getLogger().log(Level.WARNING, "Argument could not be passed.", e);
} catch (IllegalAccessException e) {
Bukkit.getLogger().log(Level.WARNING, "Could not access method.", e);
} catch (InstantiationException e) {
Bukkit.getLogger().log(Level.WARNING, "Underlying class is abstract.", e);
} catch (InvocationTargetException e) {
Bukkit.getLogger().log(Level.WARNING, "A error has occurred during invoking of method.", e);
} catch (NoSuchMethodException e) {
Bukkit.getLogger().log(Level.WARNING, "Could not find method.", e);
} catch (ClassNotFoundException e) {
Bukkit.getLogger().log(Level.WARNING, "Could not find class.", e);
}
}
// The ChatSerializer's instance of Gson
private static Object nmsChatSerializerGsonInstance;
private static Method fromJsonMethod;
private Object createChatPacket(String json) throws IllegalArgumentException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException {
if (nmsChatSerializerGsonInstance == null) {
// Find the field and its value, completely bypassing obfuscation
Class<?> chatSerializerClazz;
// Get the three parts of the version string (major version is currently unused)
// vX_Y_RZ
// X = major
// Y = minor
// Z = revision
final String version = Reflection.getVersion();
String[] split = version.substring(1, version.length() - 1).split("_"); // Remove trailing dot
//int majorVersion = Integer.parseInt(split[0]);
int minorVersion = Integer.parseInt(split[1]);
int revisionVersion = Integer.parseInt(split[2].substring(1)); // Substring to ignore R
if (minorVersion < 8 || (minorVersion == 8 && revisionVersion == 1)) {
chatSerializerClazz = Reflection.getNMSClass("ChatSerializer");
} else {
chatSerializerClazz = Reflection.getNMSClass("IChatBaseComponent$ChatSerializer");
}
if (chatSerializerClazz == null) {
throw new ClassNotFoundException("Can't find the ChatSerializer class");
}
for (Field declaredField : chatSerializerClazz.getDeclaredFields()) {
if (Modifier.isFinal(declaredField.getModifiers()) && Modifier.isStatic(declaredField.getModifiers()) && declaredField.getType().getName().endsWith("Gson")) {
// We've found our field
declaredField.setAccessible(true);
nmsChatSerializerGsonInstance = declaredField.get(null);
fromJsonMethod = nmsChatSerializerGsonInstance.getClass().getMethod("fromJson", String.class, Class.class);
break;
}
}
}
// Since the method is so simple, and all the obfuscated methods have the same name, it's easier to reimplement 'IChatBaseComponent a(String)' than to reflectively call it
// Of course, the implementation may change, but fuzzy matches might break with signature changes
Object serializedChatComponent = fromJsonMethod.invoke(nmsChatSerializerGsonInstance, json, Reflection.getNMSClass("IChatBaseComponent"));
return nmsPacketPlayOutChatConstructor.newInstance(serializedChatComponent);
}
/**
* Sends this message to a command sender.
* If the sender is a player, they will receive the fully-fledged formatted display of this message.
* Otherwise, they will receive a version of this message with less formatting.
*
* @param sender The command sender who will receive the message.
* @see #toOldMessageFormat()
*/
public void send(CommandSender sender) {
send(sender, toJSONString());
}
/**
* Sends this message to multiple command senders.
*
* @param senders The command senders who will receive the message.
* @see #send(org.bukkit.command.CommandSender)
*/
public void send(final Iterable<? extends CommandSender> senders) {
String string = toJSONString();
for (final CommandSender sender : senders) {
send(sender, string);
}
}
/**
* Convert this message to a human-readable string with limited formatting.
* This method is used to send this message to clients without JSON formatting support.
* <p>
* Serialization of this message by using this message will include (in this order for each message part):
* <ol>
* <li>The color of each message part.</li>
* <li>The applicable stylizations for each message part.</li>
* <li>The core text of the message part.</li>
* </ol>
* The primary omissions are tooltips and clickable actions. Consequently, this method should be used only as a last resort.
* </p>
* <p>
* Color and formatting can be removed from the returned string by using {@link org.bukkit.ChatColor#stripColor(String)}.</p>
*
* @return A human-readable string representing limited formatting in addition to the core text of this message.
*/
public String toOldMessageFormat() {
StringBuilder result = new StringBuilder();
for (MessagePart part : this) {
result.append(part.color == null ? "" : part.color);
for (ChatColor formatSpecifier : part.styles) {
result.append(formatSpecifier);
}
result.append(part.text);
}
return result.toString();
}
private void onCurrent(Consumer<MessagePart> call) {
for (int i = index - 1; i < messageParts.size(); i++) {
call.accept(messageParts.get(i));
}
}
private MessagePart latest() {
return messageParts.get(messageParts.size() - 1);
}
private void onClick(final String name, final String data) {
onCurrent(m -> { m.clickActionName = name; m.clickActionData = data; });
dirty = true;
}
private void onHover(final String name, final JsonRepresentedObject data) {
onCurrent(m -> { m.hoverActionName = name; m.hoverActionData = data; });
dirty = true;
}
// Doc copied from interface
public Map<String, Object> serialize() {
HashMap<String, Object> map = new HashMap<>();
map.put("messageParts", messageParts);
// map.put("JSON", toJSONString());
return map;
}
/**
* Deserializes a JSON-represented message from a mapping of key-value pairs.
* This is called by the Bukkit serialization API.
* It is not intended for direct public API consumption.
*
* @param serialized The key-value mapping which represents a fancy message.
*/
@SuppressWarnings("unchecked")
public static FancyMessage deserialize(Map<String, Object> serialized) {
FancyMessage msg = new FancyMessage();
msg.messageParts = (List<MessagePart>) serialized.get("messageParts");
msg.jsonString = serialized.containsKey("JSON") ? serialized.get("JSON").toString() : null;
msg.dirty = !serialized.containsKey("JSON");
return msg;
}
/**
* <b>Internally called method. Not for API consumption.</b>
*/
public Iterator<MessagePart> iterator() {
return messageParts.iterator();
}
private static JsonParser _stringParser = new JsonParser();
/**
* Deserializes a fancy message from its JSON representation. This JSON representation is of the format of
* that returned by {@link #toJSONString()}, and is compatible with vanilla inputs.
*
* @param json The JSON string which represents a fancy message.
* @return A {@code FancyMessage} representing the parameterized JSON message.
*/
public static FancyMessage deserialize(String json) {
JsonObject serialized = _stringParser.parse(json).getAsJsonObject();
JsonArray extra = serialized.getAsJsonArray("extra"); // Get the extra component
FancyMessage returnVal = new FancyMessage();
returnVal.messageParts.clear();
for (JsonElement mPrt : extra) {
MessagePart component = new MessagePart();
JsonObject messagePart = mPrt.getAsJsonObject();
for (Map.Entry<String, JsonElement> entry : messagePart.entrySet()) {
// Deserialize text
if (TextualComponent.isTextKey(entry.getKey())) {
// The map mimics the YAML serialization, which has a "key" field and one or more "value" fields
Map<String, Object> serializedMapForm = new HashMap<>(); // Must be object due to Bukkit serializer API compliance
serializedMapForm.put("key", entry.getKey());
if (entry.getValue().isJsonPrimitive()) {
// Assume string
serializedMapForm.put("value", entry.getValue().getAsString());
} else {
// Composite object, but we assume each element is a string
for (Map.Entry<String, JsonElement> compositeNestedElement : entry.getValue().getAsJsonObject().entrySet()) {
serializedMapForm.put("value." + compositeNestedElement.getKey(), compositeNestedElement.getValue().getAsString());
}
}
component.text = TextualComponent.deserialize(serializedMapForm);
} else if (MessagePart.stylesToNames.inverse().containsKey(entry.getKey())) {
if (entry.getValue().getAsBoolean()) {
component.styles.add(MessagePart.stylesToNames.inverse().get(entry.getKey()));
}
} else if (entry.getKey().equals("color")) {
component.color = ChatColor.valueOf(entry.getValue().getAsString().toUpperCase());
} else if (entry.getKey().equals("clickEvent")) {
JsonObject object = entry.getValue().getAsJsonObject();
component.clickActionName = object.get("action").getAsString();
component.clickActionData = object.get("value").getAsString();
} else if (entry.getKey().equals("hoverEvent")) {
JsonObject object = entry.getValue().getAsJsonObject();
component.hoverActionName = object.get("action").getAsString();
if (object.get("value").isJsonPrimitive()) {
// Assume string
component.hoverActionData = new JsonString(object.get("value").getAsString());
} else {
// Assume composite type
// The only composite type we currently store is another FancyMessage
// Therefore, recursion time!
component.hoverActionData = deserialize(object.get("value").toString() /* This should properly serialize the JSON object as a JSON string */);
}
} else if (entry.getKey().equals("insertion")) {
component.insertionData = entry.getValue().getAsString();
} else if (entry.getKey().equals("with")) {
for (JsonElement object : entry.getValue().getAsJsonArray()) {
if (object.isJsonPrimitive()) {
component.translationReplacements.add(new JsonString(object.getAsString()));
} else {
// Only composite type stored in this array is - again - FancyMessages
// Recurse within this function to parse this as a translation replacement
component.translationReplacements.add(deserialize(object.toString()));
}
}
}
}
returnVal.messageParts.add(component);
returnVal.index = returnVal.messageParts.size();
}
return returnVal;
}
}

View File

@ -0,0 +1,18 @@
package com.boydti.fawe.bukkit.chat;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
/**
* Represents an object that can be serialized to a JSON writer instance.
*/
interface JsonRepresentedObject {
/**
* Writes the JSON representation of this object to the specified writer.
* @param writer The JSON writer which will receive the object.
* @throws java.io.IOException If an error occurs writing to the stream.
*/
public void writeJson(JsonWriter writer) throws IOException;
}

View File

@ -0,0 +1,46 @@
package com.boydti.fawe.bukkit.chat;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
/**
* Represents a JSON string value.
* Writes by this object will not write name values nor begin/end objects in the JSON stream.
* All writes merely write the represented string value.
*/
final class JsonString implements JsonRepresentedObject, ConfigurationSerializable {
private String _value;
public JsonString(CharSequence value) {
_value = value == null ? null : value.toString();
}
@Override
public void writeJson(JsonWriter writer) throws IOException {
writer.value(getValue());
}
public String getValue() {
return _value;
}
public Map<String, Object> serialize() {
HashMap<String, Object> theSingleValue = new HashMap<String, Object>();
theSingleValue.put("stringValue", _value);
return theSingleValue;
}
public static JsonString deserialize(Map<String, Object> map) {
return new JsonString(map.get("stringValue").toString());
}
@Override
public String toString() {
return _value;
}
}

View File

@ -0,0 +1,156 @@
package com.boydti.fawe.bukkit.chat;
import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.configuration.serialization.ConfigurationSerialization;
/**
* Internal class: Represents a component of a JSON-serializable {@link FancyMessage}.
*/
final class MessagePart implements JsonRepresentedObject, ConfigurationSerializable, Cloneable {
ChatColor color = ChatColor.WHITE;
ArrayList<ChatColor> styles = new ArrayList<>();
String clickActionName = null;
String clickActionData = null;
String hoverActionName = null;
JsonRepresentedObject hoverActionData = null;
TextualComponent text = null;
String insertionData = null;
ArrayList<JsonRepresentedObject> translationReplacements = new ArrayList<>();
MessagePart(final TextualComponent text) {
this.text = text;
}
MessagePart() {
this.text = null;
}
boolean hasText() {
return text != null;
}
@Override
@SuppressWarnings("unchecked")
public MessagePart clone() throws CloneNotSupportedException {
MessagePart obj = (MessagePart) super.clone();
obj.styles = (ArrayList<ChatColor>) styles.clone();
if (hoverActionData instanceof JsonString) {
obj.hoverActionData = new JsonString(((JsonString) hoverActionData).getValue());
} else if (hoverActionData instanceof FancyMessage) {
obj.hoverActionData = ((FancyMessage) hoverActionData).clone();
}
obj.translationReplacements = (ArrayList<JsonRepresentedObject>) translationReplacements.clone();
return obj;
}
static final BiMap<ChatColor, String> stylesToNames;
static {
ImmutableBiMap.Builder<ChatColor, String> builder = ImmutableBiMap.builder();
for (final ChatColor style : ChatColor.values()) {
if (!style.isFormat()) {
continue;
}
String styleName;
switch (style) {
case MAGIC:
styleName = "obfuscated";
break;
case UNDERLINE:
styleName = "underlined";
break;
default:
styleName = style.name().toLowerCase();
break;
}
builder.put(style, styleName);
}
stylesToNames = builder.build();
}
public void writeJson(JsonWriter json) {
try {
json.beginObject();
text.writeJson(json);
json.name("color").value(color.name().toLowerCase());
for (final ChatColor style : styles) {
json.name(stylesToNames.get(style)).value(true);
}
if (clickActionName != null && clickActionData != null) {
json.name("clickEvent")
.beginObject()
.name("action").value(clickActionName)
.name("value").value(clickActionData)
.endObject();
}
if (hoverActionName != null && hoverActionData != null) {
json.name("hoverEvent")
.beginObject()
.name("action").value(hoverActionName)
.name("value");
hoverActionData.writeJson(json);
json.endObject();
}
if (insertionData != null) {
json.name("insertion").value(insertionData);
}
if (translationReplacements.size() > 0 && text != null && TextualComponent.isTranslatableText(text)) {
json.name("with").beginArray();
for (JsonRepresentedObject obj : translationReplacements) {
obj.writeJson(json);
}
json.endArray();
}
json.endObject();
} catch (IOException e) {
Bukkit.getLogger().log(Level.WARNING, "A problem occured during writing of JSON string", e);
}
}
public Map<String, Object> serialize() {
HashMap<String, Object> map = new HashMap<>();
map.put("text", text);
map.put("styles", styles);
map.put("color", color.getChar());
map.put("hoverActionName", hoverActionName);
map.put("hoverActionData", hoverActionData);
map.put("clickActionName", clickActionName);
map.put("clickActionData", clickActionData);
map.put("insertion", insertionData);
map.put("translationReplacements", translationReplacements);
return map;
}
@SuppressWarnings("unchecked")
public static MessagePart deserialize(Map<String, Object> serialized) {
MessagePart part = new MessagePart((TextualComponent) serialized.get("text"));
part.styles = (ArrayList<ChatColor>) serialized.get("styles");
part.color = ChatColor.getByChar(serialized.get("color").toString());
part.hoverActionName = (String) serialized.get("hoverActionName");
part.hoverActionData = (JsonRepresentedObject) serialized.get("hoverActionData");
part.clickActionName = (String) serialized.get("clickActionName");
part.clickActionData = (String) serialized.get("clickActionData");
part.insertionData = (String) serialized.get("insertion");
part.translationReplacements = (ArrayList<JsonRepresentedObject>) serialized.get("translationReplacements");
return part;
}
static {
ConfigurationSerialization.registerClass(MessagePart.class);
}
}

View File

@ -0,0 +1,205 @@
package com.boydti.fawe.bukkit.chat;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.bukkit.Bukkit;
/**
* A class containing static utility methods and caches which are intended as reflective conveniences.
* Unless otherwise noted, upon failure methods will return {@code null}.
*/
public final class Reflection {
/**
* Stores loaded classes from the {@code net.minecraft.server} package.
*/
private static final Map<String, Class<?>> _loadedNMSClasses = new HashMap<>();
/**
* Stores loaded classes from the {@code org.bukkit.craftbukkit} package (and subpackages).
*/
private static final Map<String, Class<?>> _loadedOBCClasses = new HashMap<>();
private static final Map<Class<?>, Map<String, Field>> _loadedFields = new HashMap<>();
/**
* Contains loaded methods in a cache.
* The map maps [types to maps of [method names to maps of [parameter types to method instances]]].
*/
private static final Map<Class<?>, Map<String, Map<ArrayWrapper<Class<?>>, Method>>> _loadedMethods = new HashMap<>();
private static String _versionString;
private Reflection() { }
/**
* Gets the version string from the package name of the CraftBukkit server implementation.
* This is needed to bypass the JAR package name changing on each update.
*
* @return The version string of the OBC and NMS packages, <em>including the trailing dot</em>.
*/
public synchronized static String getVersion() {
if (_versionString == null) {
if (Bukkit.getServer() == null) {
// The server hasn't started, static initializer call?
return null;
}
String name = Bukkit.getServer().getClass().getPackage().getName();
_versionString = name.substring(name.lastIndexOf('.') + 1) + ".";
}
return _versionString;
}
/**
* Gets a {@link Class} object representing a type contained within the {@code net.minecraft.server} versioned package.
* The class instances returned by this method are cached, such that no lookup will be done twice (unless multiple threads are accessing this method simultaneously).
*
* @param className The name of the class, excluding the package, within NMS.
* @return The class instance representing the specified NMS class, or {@code null} if it could not be loaded.
*/
public synchronized static Class<?> getNMSClass(String className) {
if (_loadedNMSClasses.containsKey(className)) {
return _loadedNMSClasses.get(className);
}
String fullName = "net.minecraft.server." + getVersion() + className;
Class<?> clazz;
try {
clazz = Class.forName(fullName);
} catch (ClassNotFoundException e) {
_loadedNMSClasses.put(className, null);
throw new RuntimeException(e);
}
_loadedNMSClasses.put(className, clazz);
return clazz;
}
/**
* Gets a {@link Class} object representing a type contained within the {@code org.bukkit.craftbukkit} versioned package.
* The class instances returned by this method are cached, such that no lookup will be done twice (unless multiple threads are accessing this method simultaneously).
*
* @param className The name of the class, excluding the package, within OBC. This name may contain a subpackage name, such as {@code inventory.CraftItemStack}.
* @return The class instance representing the specified OBC class, or {@code null} if it could not be loaded.
*/
public synchronized static Class<?> getOBCClass(String className) {
if (_loadedOBCClasses.containsKey(className)) {
return _loadedOBCClasses.get(className);
}
String fullName = "org.bukkit.craftbukkit." + getVersion() + className;
Class<?> clazz;
try {
clazz = Class.forName(fullName);
} catch (ClassNotFoundException e) {
_loadedOBCClasses.put(className, null);
throw new RuntimeException(e);
}
_loadedOBCClasses.put(className, clazz);
return clazz;
}
/**
* Attempts to get the NMS handle of a CraftBukkit object.
* <p>
* 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.
* </p>
*
* @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.
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
*
* @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<String, Field> 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* This method does <em>not</em> 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<String, Map<ArrayWrapper<Class<?>>, Method>>());
}
Map<String, Map<ArrayWrapper<Class<?>>, Method>> loadedMethodNames = _loadedMethods.get(clazz);
if (!loadedMethodNames.containsKey(name)) {
loadedMethodNames.put(name, new HashMap<ArrayWrapper<Class<?>>, Method>());
}
Map<ArrayWrapper<Class<?>>, Method> loadedSignatures = loadedMethodNames.get(name);
ArrayWrapper<Class<?>> 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;
}
}

View File

@ -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.
* <p>Different instances of this class can be created with static constructor methods.</p>
*/
public abstract class TextualComponent implements Cloneable {
static {
ConfigurationSerialization.registerClass(TextualComponent.ArbitraryTextTypeComponent.class);
ConfigurationSerialization.registerClass(TextualComponent.ComplexTextTypeComponent.class);
}
static TextualComponent deserialize(Map<String, Object> 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.
*
* <p>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 <em>key</em>, which can be overridden by a
* resource pack.
* <p>
* 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.
* </p>
*
* @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.
* <p>
* <b>This method is currently guaranteed to throw an {@code UnsupportedOperationException} as it is only supported on snapshot clients.</b>
* </p>
*
* @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.
*
* <p><b>This method is currently guaranteed to throw an {@code UnsupportedOperationException}
* as it is only supported on snapshot clients.</b>
*
* @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 <em>not</em> 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.<String, String>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.
* <p>
* <b>This method is currently guaranteed to throw an {@code UnsupportedOperationException} as it is only supported on snapshot clients.</b>
* </p>
*
* @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<String, Object> 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<String, Object> serialize() {
return new HashMap<String, Object>() {
{
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.
*
* <p>Exception validating done is on keys and values.
*/
private static final class ComplexTextTypeComponent extends TextualComponent implements ConfigurationSerializable {
private String key;
private Map<String, String> value;
public ComplexTextTypeComponent(String key, Map<String, String> values) {
setKey(key);
setValue(values);
}
public static ComplexTextTypeComponent deserialize(Map<String, Object> map) {
String key = null;
Map<String, String> value = new HashMap<>();
for (Map.Entry<String, Object> 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<String, String> getValue() {
return value;
}
public void setValue(Map<String, String> 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<String, String> jsonPair : value.entrySet()) {
writer.name(jsonPair.getKey()).value(jsonPair.getValue());
}
writer.endObject();
}
@Override
@SuppressWarnings("serial")
public Map<String, Object> serialize() {
return new HashMap<String, Object>() {
{
put("key", getKey());
for (Entry<String, String> valEntry : getValue().entrySet()) {
put("value." + valEntry.getKey(), valEntry.getValue());
}
}
};
}
@Override
public String getReadableString() {
return getKey();
}
}
}

View File

@ -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<CHUNK, CHUNKSECTIONS, SECTION> 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<CHUNK, CHUNKSECTIONS, SECTION> 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<CHUNK, CHUNKSECTIONS, SECTION> 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<CHUNK, CHUNKSECTIONS, SECTION> extends NMSMa
e.printStackTrace();
}
}
parallelThreads = 0;
}
@Override

View File

@ -0,0 +1,204 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -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'

View File

@ -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("=======================================");
}

View File

@ -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 <T>
* @return
*/
@Deprecated
public static <G, T extends MCAFilter<G>> 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 <T>
* @return
*/
@Deprecated
public static <G, T extends MCAFilter<G>> 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 = "<folder>",
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 = "<folder> <age-ticks> [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 = "<folder> <time>",
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",

View File

@ -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<String, String> 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<String, Object> obj, String color, Map<String, Boolean> properties) {
Object[] style = new Object[] { color, properties };
for (Map.Entry<String, Object> 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<String, Object> 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<Map<String, Object>> list = (List<Map<String, Object>>) entry.getValue();
for (Map<String, Object> elem : list) {
elem.putIfAbsent("color", obj.get("color"));
for (Map.Entry<String, Object> 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<String, Object> obj = gson.fromJson(text, new TypeToken<Map<String, Object>>() {}.getType());
HashMap<String, Boolean> 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

View File

@ -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<String> aliases = commands.getStringList("aliases");
List<String> aliases = cmdConfig.getStringList(clazz + "." + command + ".aliases");
if (aliases == null) {
aliases = cmdConfig.getStringList(command + ".aliases");
}
return (aliases == null || aliases.isEmpty()) ? command : aliases.get(0);
}

View File

@ -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;
}
}

View File

@ -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);
}
});
}
}

View File

@ -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

View File

@ -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
}
}

View File

@ -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();

View File

@ -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

View File

@ -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;
}
}

View File

@ -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() {

View File

@ -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();
}
});
}
}

View File

@ -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) {

View File

@ -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;
}
}
}

View File

@ -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<Void> {
}
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

View File

@ -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);
}

View File

@ -83,4 +83,14 @@ public class MCAFilter<T> extends IterableThreadLocal<T> {
*/
public void finishChunk(MCAChunk chunk, T cache) {
}
/**
* Do something with the MCAChunk after block filtering<br>
*
* @param chunk
* @param cache
* @return
*/
public void finishFile(MCAFile file, T cache) {
}
}

View File

@ -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<FaweQueue, FaweChunk, FaweChunk
}
public <G, T extends MCAFilter<G>> T filterCopy(final T filter, boolean deleteOnCopyFail) {
this.filterWorld(new MCAFilter<G>() {
@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<G>(filter) {
@Override
public MCAFile applyFile(MCAFile mca) {
File file = mca.getFile();
@ -328,21 +318,6 @@ public class MCAQueue extends NMSMappedFaweQueue<FaweQueue, FaweChunk, FaweChunk
return null;
}
}
@Override
public boolean appliesChunk(int cx, int cz) {
return filter.appliesChunk(cx, cz);
}
@Override
public MCAChunk applyChunk(MCAChunk chunk, G cache) {
return filter.applyChunk(chunk, cache);
}
@Override
public void applyBlock(int x, int y, int z, BaseBlock block, G cache) {
filter.applyBlock(x, y, z, block, cache);
}
}, true, new RunnableVal<MCAFile>() {
@Override
public void run(MCAFile value) {
@ -360,7 +335,7 @@ public class MCAQueue extends NMSMappedFaweQueue<FaweQueue, FaweChunk, FaweChunk
}
public <G, T extends MCAFilter<G>> T filterRegion(final T filter, final RegionWrapper region) {
this.filterWorld(new MCAFilter<G>() {
this.filterWorld(new DelegateMCAFilter<G>(filter) {
@Override
public boolean appliesFile(Path path, BasicFileAttributes attr) {
@ -376,11 +351,6 @@ public class MCAQueue extends NMSMappedFaweQueue<FaweQueue, FaweChunk, FaweChunk
return region.isInMCA(mcaX, mcaZ) && filter.appliesFile(mcaX, mcaZ);
}
@Override
public MCAFile applyFile(MCAFile file) {
return filter.applyFile(file);
}
@Override
public boolean appliesChunk(int cx, int cz) {
return region.isInChunk(cx, cz) && filter.appliesChunk(cx, cz);
@ -433,11 +403,6 @@ public class MCAQueue extends NMSMappedFaweQueue<FaweQueue, FaweChunk, FaweChunk
}
return null;
}
@Override
public void finishChunk(MCAChunk chunk, G cache) {
super.finishChunk(chunk, cache);
}
});
return filter;
}
@ -537,15 +502,19 @@ public class MCAQueue extends NMSMappedFaweQueue<FaweQueue, FaweChunk, FaweChunk
});
}
});
} else if (original.isDeleted()) {
try {
original.close(pool);
file.delete();
} catch (Throwable ignore) {
ignore.printStackTrace();
pool.awaitQuiescence(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
filter.finishFile(finalFile, filter.get());
} else {
if (original.isDeleted()) {
try {
original.close(pool);
file.delete();
} catch (Throwable ignore) {
ignore.printStackTrace();
}
}
pool.awaitQuiescence(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
}
pool.awaitQuiescence(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
original.close(pool);
if (original.isDeleted()) {
file.delete();

View File

@ -187,7 +187,7 @@ public abstract class MCAWriter {
int cx = (fmcaX << 5) + (i & 31);
int cz = (fmcaZ << 5) + (i >> 5);
raf.seek(offset);
raf.writeInt(compressedBytes.length);
raf.writeInt(compressedBytes.length + 1);
raf.write(2);
raf.write(compressedBytes);
offset += blocks * 4096;

View File

@ -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;
}

View File

@ -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<T> extends MCAFilter<T> {
private final MCAFilter<T> 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<T> spliterator() {
return filter.spliterator();
}
@Override
public void remove() {
filter.remove();
}
@Override
public void set(T value) {
filter.set(value);
}
public DelegateMCAFilter(MCAFilter<T> filter) {
this.filter = filter;
}
}

View File

@ -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<String, Tag> 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<String, Object> 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;
}
}
}
}

View File

@ -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;

View File

@ -66,7 +66,7 @@ public abstract class FawePlayer<T> extends Metadatable {
* @return
*/
public static <V> FawePlayer<V> wrap(Object obj) {
if (obj == null) {
if (obj == null || (obj instanceof String && obj.equals("*"))) {
return FakePlayer.getConsole().toFawePlayer();
}
if (obj instanceof FawePlayer) {

View File

@ -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;
}

View File

@ -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

View File

@ -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);

View File

@ -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) {

View File

@ -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<BaseBlock, BaseBlock> 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<BaseBlock, BaseBlock> 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;
}
}

View File

@ -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<IntegerTrio, CompoundTag> 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);

View File

@ -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);

View File

@ -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) {

View File

@ -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);

View File

@ -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());
}
}
});
}
}

View File

@ -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();

View File

@ -0,0 +1,14 @@
package com.boydti.fawe.object.collection;
public class ByteStore extends IterableThreadLocal<byte[]> {
private final int size;
public ByteStore(int size) {
this.size = size;
}
@Override
public byte[] init() {
return new byte[size];
}
}

View File

@ -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 <code>byte</code> 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 <code>length</code> bytes from the specified byte array
* starting at <code>offset</code> 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 <code>boolean</code> 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 <code>0</code> in written.
*
* @param b the <code>boolean</code> 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 <code>byte</code> to the underlying output stream
*
* @param b the <code>byte</code> 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 <code>short</code> to the underlying output stream in
* little endian order, low byte first.
*
* @param s the <code>short</code> 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 <code>char</code> to the underlying output stream
* in little endian order, low byte first.
*
* @param c the <code>char</code> 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 <code>int</code> to the underlying output stream
* in little endian order, low byte first, high byte last
*
* @param i the <code>int</code> 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 <code>long</code> to the underlying output stream
* in little endian order, low byte first, high byte last
*
* @param l the <code>long</code> 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 <code>float</code> 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 <code>double</code> 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 <code>writeByte()</code> method.
*
* @param s the <code>String</code> 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 <code>writeChar</code> method.
*
* @param s a <code>String</code> 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 <b>big</b> 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 <code>written</code> field.
* @see java.io.DataOutputStream#written
*/
public int size() {
return this.written;
}
}

View File

@ -41,8 +41,8 @@ public class PGZIPOutputStream extends FilterOutputStream {
// array list that contains the block sizes
ArrayList<Integer> blockSizes = new ArrayList<Integer>();
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() {

View File

@ -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.
* <p>
* <p>
* ... [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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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];
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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));

View File

@ -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);

View File

@ -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);

View File

@ -58,8 +58,6 @@ public class FaweMask {
return false;
}
;
public BlockVector[] getBounds() {
final BlockVector[] BlockVectors = {this.position1, this.position2};
return BlockVectors;

View File

@ -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;

View File

@ -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");

View File

@ -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);
}
}
}

View File

@ -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();

View File

@ -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;
}
}
}

View File

@ -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<String, String> replacements) {
@ -32,6 +33,25 @@ public class StringMan {
return sb.toString();
}
public static String removeFromSet(final String string, final Collection<String> 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 <T> String join(Collection<T> arr, final String delimiter, Function<T, String> 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++) {

View File

@ -453,6 +453,7 @@ public class TextureUtil {
}
public void loadModTextures() throws IOException {
BundledBlockData.getInstance().loadFromResource();
Int2ObjectOpenHashMap<Integer> colorMap = new Int2ObjectOpenHashMap<>();
Int2ObjectOpenHashMap<Long> distanceMap = new Int2ObjectOpenHashMap<>();
Gson gson = new Gson();

View File

@ -0,0 +1,21 @@
package com.boydti.fawe.util.chat;
import com.boydti.fawe.object.FawePlayer;
public interface ChatManager<T> {
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);
}

View File

@ -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> T $(ChatManager<T> manager) {
return (T) this.builder;
}
public <T> T reset(ChatManager<T> 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;
}
}

View File

@ -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<List<StringBuilder>> {
@Override
public List<StringBuilder> builder() {
return new ArrayList<>();
}
@Override
public void color(Message message, String color) {
List<StringBuilder> 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) {}
}

View File

@ -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() {

View File

@ -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<Vector> vset, final Pattern pattern) throws MaxChangedBlocksException {
public int setBlocks(final Set<Vector> 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<BaseBlock> filter, final BaseBlock replacement) throws MaxChangedBlocksException {
public int replaceBlocks(final Region region, final Set<BaseBlock> 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<BaseBlock> filter, final Pattern pattern) throws MaxChangedBlocksException {
public int replaceBlocks(final Region region, final Set<BaseBlock> 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<Vector> 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<Vector> 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<Node> nodes = new ArrayList<Node>(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;
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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<BaseBiome> 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(

View File

@ -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
)

View File

@ -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);

View File

@ -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) {

View File

@ -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 = "[<format>] <filename>", desc = "Load a schematic into your clipboard")
@Deprecated
@CommandPermissions({"worldedit.clipboard.load", "worldedit.schematic.load", "worldedit.schematic.upload"})

View File

@ -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);
}

View File

@ -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) {

View File

@ -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<String> visited = new ArrayList<String>();
@ -748,10 +752,11 @@ public class UtilityCommands extends MethodCommands {
// Get a list of aliases
List<CommandMapping> aliases = new ArrayList<CommandMapping>(dispatcher.getCommands());
List<String> prefixes = Collections.nCopies(aliases.size(), "");
// Group by callable
if (page == -1 || effectiveLength > 0) {
Map<String, Set<CommandMapping>> grouped = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
Map<String, Map<CommandMapping, String>> 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<CommandMapping> queue = grouped.get(group);
Map<CommandMapping, String> 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<CommandMapping> mappings = effectiveLength == 1 ? grouped.get(cat) : null;
Map<CommandMapping, String> 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<CommandMapping>(dispatcher.getCommands());
prefixes = Collections.nCopies(aliases.size(), "");
} else {
aliases = new ArrayList<>(mappings);
visited.add(cat);
aliases = new ArrayList<>();
prefixes = new ArrayList<>();
for (Map.Entry<CommandMapping, String> 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<String, Set<CommandMapping>> entry : grouped.entrySet()) {
String s1 = "&a//help " + entry.getKey();
for (Map.Entry<String, Map<CommandMapping, String>> 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<CommandMapping> 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<CommandMapping> subAliases = aliases.subList(offset, end);
List<String> 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))));

View File

@ -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);

View File

@ -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;

View File

@ -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()));
}

View File

@ -0,0 +1,102 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -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.'

View File

@ -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 сохранен.'

93
forge112/build.gradle Normal file
View File

@ -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)

View File

@ -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<String, FaweCommand> commands = new HashMap<>();
public HashMap<String, FaweCommand> 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<FaweMaskManager> 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;
}
}

View File

@ -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<Object> fp = FawePlayer.wrap(player);
cmd.executeSafe(fp, args);
}
}
}

Some files were not shown because too many files have changed in this diff Show More