diff --git a/core/src/main/java/com/boydti/fawe/object/clipboard/ItemWikiScraper.java b/core/src/main/java/com/boydti/fawe/object/clipboard/ItemWikiScraper.java index b54dbc8e..254278e2 100644 --- a/core/src/main/java/com/boydti/fawe/object/clipboard/ItemWikiScraper.java +++ b/core/src/main/java/com/boydti/fawe/object/clipboard/ItemWikiScraper.java @@ -1,7 +1,6 @@ package com.boydti.fawe.object.clipboard; -import com.boydti.fawe.Fawe; import com.boydti.fawe.util.MainUtil; import com.google.common.io.Resources; import com.google.common.reflect.TypeToken; @@ -58,7 +57,6 @@ public class ItemWikiScraper { } private Map scrape(ClipboardRemapper.RemapPlatform platform) throws IOException { - Fawe.debug("Downloading item mappings for " + platform + ". Please wait..."); String url = (platform == ClipboardRemapper.RemapPlatform.PC) ? PC : PE; String text = MainUtil.getText(url); @@ -96,7 +94,6 @@ public class ItemWikiScraper { } id++; } - Fawe.debug("Download complete."); return map; } } diff --git a/core/src/main/java/com/sk89q/jnbt/CompoundTag.java b/core/src/main/java/com/sk89q/jnbt/CompoundTag.java new file mode 100644 index 00000000..a1131935 --- /dev/null +++ b/core/src/main/java/com/sk89q/jnbt/CompoundTag.java @@ -0,0 +1,409 @@ +package com.sk89q.jnbt; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * The {@code TAG_Compound} tag. + */ +public final class CompoundTag extends Tag { + + private final Map value; + /** + * Creates the tag with an empty name. + * + * @param value the value of the tag + */ + public CompoundTag(Map value) { + super(); + this.value = Collections.unmodifiableMap(value); + } + + @Override + public Map getRaw() { + HashMap raw = new HashMap<>(); + for (Map.Entry entry : value.entrySet()) { + raw.put(entry.getKey(), entry.getValue().getRaw()); + } + return raw; + } + + /** + * Returns whether this compound tag contains the given key. + * + * @param key the given key + * @return true if the tag contains the given key + */ + public boolean containsKey(String key) { + return value.containsKey(key); + } + + @Override + public Map getValue() { + return value; + } + + /** + * Return a new compound tag with the given values. + * + * @param value the value + * @return the new compound tag + */ + public CompoundTag setValue(Map value) { + return new CompoundTag(value); + } + + /** + * Create a compound tag builder. + * + * @return the builder + */ + public CompoundTagBuilder createBuilder() { + return new CompoundTagBuilder(new HashMap(value)); + } + + /** + * Get a byte array named with the given key. + * + *

If the key does not exist or its value is not a byte array tag, + * then an empty byte array will be returned.

+ * + * @param key the key + * @return a byte array + */ + public byte[] getByteArray(String key) { + Tag tag = value.get(key); + if (tag instanceof ByteArrayTag) { + return ((ByteArrayTag) tag).getValue(); + } else { + return new byte[0]; + } + } + + /** + * Get a byte named with the given key. + * + *

If the key does not exist or its value is not a byte tag, + * then {@code 0} will be returned.

+ * + * @param key the key + * @return a byte + */ + public byte getByte(String key) { + Tag tag = value.get(key); + if (tag instanceof ByteTag) { + return ((ByteTag) tag).getValue(); + } else { + return (byte) 0; + } + } + + /** + * Get a double named with the given key. + * + *

If the key does not exist or its value is not a double tag, + * then {@code 0} will be returned.

+ * + * @param key the key + * @return a double + */ + public double getDouble(String key) { + Tag tag = value.get(key); + if (tag instanceof DoubleTag) { + return ((DoubleTag) tag).getValue(); + } else { + return 0; + } + } + + /** + * Get a double named with the given key, even if it's another + * type of number. + * + *

If the key does not exist or its value is not a number, + * then {@code 0} will be returned.

+ * + * @param key the key + * @return a double + */ + public double asDouble(String key) { + Tag tag = value.get(key); + if (tag instanceof ByteTag) { + return ((ByteTag) tag).getValue(); + + } else if (tag instanceof ShortTag) { + return ((ShortTag) tag).getValue(); + + } else if (tag instanceof IntTag) { + return ((IntTag) tag).getValue(); + + } else if (tag instanceof LongTag) { + return ((LongTag) tag).getValue(); + + } else if (tag instanceof FloatTag) { + return ((FloatTag) tag).getValue(); + + } else if (tag instanceof DoubleTag) { + return ((DoubleTag) tag).getValue(); + + } else { + return 0; + } + } + + /** + * Get a float named with the given key. + * + *

If the key does not exist or its value is not a float tag, + * then {@code 0} will be returned.

+ * + * @param key the key + * @return a float + */ + public float getFloat(String key) { + Tag tag = value.get(key); + if (tag instanceof FloatTag) { + return ((FloatTag) tag).getValue(); + } else { + return 0; + } + } + + /** + * Get a {@code int[]} named with the given key. + * + *

If the key does not exist or its value is not an int array tag, + * then an empty array will be returned.

+ * + * @param key the key + * @return an int array + */ + public int[] getIntArray(String key) { + Tag tag = value.get(key); + if (tag instanceof IntArrayTag) { + return ((IntArrayTag) tag).getValue(); + } else { + return new int[0]; + } + } + + /** + * Get an int named with the given key. + * + *

If the key does not exist or its value is not an int tag, + * then {@code 0} will be returned.

+ * + * @param key the key + * @return an int + */ + public int getInt(String key) { + Tag tag = value.get(key); + if (tag instanceof IntTag) { + return ((IntTag) tag).getValue(); + } else { + return 0; + } + } + + /** + * Get an int named with the given key, even if it's another + * type of number. + * + *

If the key does not exist or its value is not a number, + * then {@code 0} will be returned.

+ * + * @param key the key + * @return an int + */ + public int asInt(String key) { + Tag tag = value.get(key); + if (tag instanceof ByteTag) { + return ((ByteTag) tag).getValue(); + + } else if (tag instanceof ShortTag) { + return ((ShortTag) tag).getValue(); + + } else if (tag instanceof IntTag) { + return ((IntTag) tag).getValue(); + + } else if (tag instanceof LongTag) { + return ((LongTag) tag).getValue().intValue(); + + } else if (tag instanceof FloatTag) { + return ((FloatTag) tag).getValue().intValue(); + + } else if (tag instanceof DoubleTag) { + return ((DoubleTag) tag).getValue().intValue(); + + } else { + return 0; + } + } + + /** + * Get a list of tags named with the given key. + * + *

If the key does not exist or its value is not a list tag, + * then an empty list will be returned.

+ * + * @param key the key + * @return a list of tags + */ + public List getList(String key) { + Tag tag = value.get(key); + if (tag instanceof ListTag) { + return ((ListTag) tag).getValue(); + } else { + return Collections.emptyList(); + } + } + + /** + * Get a {@code TagList} named with the given key. + * + *

If the key does not exist or its value is not a list tag, + * then an empty tag list will be returned.

+ * + * @param key the key + * @return a tag list instance + */ + public ListTag getListTag(String key) { + Tag tag = value.get(key); + if (tag instanceof ListTag) { + return (ListTag) tag; + } else { + return new ListTag(StringTag.class, Collections.emptyList()); + } + } + + /** + * Get a list of tags named with the given key. + * + *

If the key does not exist or its value is not a list tag, + * then an empty list will be returned. If the given key references + * a list but the list of of a different type, then an empty + * list will also be returned.

+ * + * @param key the key + * @param listType the class of the contained type + * @return a list of tags + * @param the type of list + */ + @SuppressWarnings("unchecked") + public List getList(String key, Class listType) { + Tag tag = value.get(key); + if (tag instanceof ListTag) { + ListTag listTag = (ListTag) tag; + if (listTag.getType().equals(listType)) { + return (List) listTag.getValue(); + } else { + return Collections.emptyList(); + } + } else { + return Collections.emptyList(); + } + } + + /** + * Get a long named with the given key. + * + *

If the key does not exist or its value is not a long tag, + * then {@code 0} will be returned.

+ * + * @param key the key + * @return a long + */ + public long getLong(String key) { + Tag tag = value.get(key); + if (tag instanceof LongTag) { + return ((LongTag) tag).getValue(); + } else { + return 0L; + } + } + + /** + * Get a long named with the given key, even if it's another + * type of number. + * + *

If the key does not exist or its value is not a number, + * then {@code 0} will be returned.

+ * + * @param key the key + * @return a long + */ + public long asLong(String key) { + Tag tag = value.get(key); + if (tag instanceof ByteTag) { + return ((ByteTag) tag).getValue(); + + } else if (tag instanceof ShortTag) { + return ((ShortTag) tag).getValue(); + + } else if (tag instanceof IntTag) { + return ((IntTag) tag).getValue(); + + } else if (tag instanceof LongTag) { + return ((LongTag) tag).getValue(); + + } else if (tag instanceof FloatTag) { + return ((FloatTag) tag).getValue().longValue(); + + } else if (tag instanceof DoubleTag) { + return ((DoubleTag) tag).getValue().longValue(); + + } else { + return 0L; + } + } + + /** + * Get a short named with the given key. + * + *

If the key does not exist or its value is not a short tag, + * then {@code 0} will be returned.

+ * + * @param key the key + * @return a short + */ + public short getShort(String key) { + Tag tag = value.get(key); + if (tag instanceof ShortTag) { + return ((ShortTag) tag).getValue(); + } else { + return 0; + } + } + + /** + * Get a string named with the given key. + * + *

If the key does not exist or its value is not a string tag, + * then {@code ""} will be returned.

+ * + * @param key the key + * @return a string + */ + public String getString(String key) { + Tag tag = value.get(key); + if (tag instanceof StringTag) { + return ((StringTag) tag).getValue(); + } else { + return ""; + } + } + + @Override + public String toString() { + StringBuilder bldr = new StringBuilder(); + bldr.append("TAG_Compound").append(": ").append(value.size()).append(" entries\r\n{\r\n"); + for (Map.Entry entry : value.entrySet()) { + bldr.append(" ").append(entry.getValue().toString().replaceAll("\r\n", "\r\n ")).append("\r\n"); + } + bldr.append("}"); + return bldr.toString(); + } + +} \ No newline at end of file diff --git a/core/src/main/java/com/sk89q/jnbt/ListTag.java b/core/src/main/java/com/sk89q/jnbt/ListTag.java new file mode 100644 index 00000000..6e516cce --- /dev/null +++ b/core/src/main/java/com/sk89q/jnbt/ListTag.java @@ -0,0 +1,422 @@ +package com.sk89q.jnbt; + +import java.util.ArrayList; +import javax.annotation.Nullable; +import java.util.Collections; +import java.util.List; +import java.util.NoSuchElementException; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * The {@code TAG_List} tag. + */ +public final class ListTag extends Tag { + + private final Class type; + private final List value; + + /** + * Creates the tag with an empty name. + * + * @param type the type of tag + * @param value the value of the tag + */ + public ListTag(Class type, List value) { + super(); + checkNotNull(value); + this.type = type; + this.value = Collections.unmodifiableList(value); + } + + @Override + public List getRaw() { + ArrayList raw = new ArrayList<>(); + for (Tag t : value) { + raw.add(t.getRaw()); + } + return raw; + } + + /** + * Gets the type of item in this list. + * + * @return The type of item in this list. + */ + public Class getType() { + return type; + } + + @Override + public List getValue() { + return value; + } + + /** + * Create a new list tag with this tag's name and type. + * + * @param list the new list + * @return a new list tag + */ + public ListTag setValue(List list) { + return new ListTag(getType(), list); + } + + /** + * Get the tag if it exists at the given index. + * + * @param index the index + * @return the tag or null + */ + @Nullable + public Tag getIfExists(int index) { + try { + return value.get(index); + } catch (NoSuchElementException e) { + return null; + } + } + + /** + * Get a byte array named with the given index. + * + *

If the index does not exist or its value is not a byte array tag, + * then an empty byte array will be returned.

+ * + * @param index the index + * @return a byte array + */ + public byte[] getByteArray(int index) { + Tag tag = getIfExists(index); + if (tag instanceof ByteArrayTag) { + return ((ByteArrayTag) tag).getValue(); + } else { + return new byte[0]; + } + } + + /** + * Get a byte named with the given index. + * + *

If the index does not exist or its value is not a byte tag, + * then {@code 0} will be returned.

+ * + * @param index the index + * @return a byte + */ + public byte getByte(int index) { + Tag tag = getIfExists(index); + if (tag instanceof ByteTag) { + return ((ByteTag) tag).getValue(); + } else { + return (byte) 0; + } + } + + /** + * Get a double named with the given index. + * + *

If the index does not exist or its value is not a double tag, + * then {@code 0} will be returned.

+ * + * @param index the index + * @return a double + */ + public double getDouble(int index) { + Tag tag = getIfExists(index); + if (tag instanceof DoubleTag) { + return ((DoubleTag) tag).getValue(); + } else { + return 0; + } + } + + /** + * Get a double named with the given index, even if it's another + * type of number. + * + *

If the index does not exist or its value is not a number, + * then {@code 0} will be returned.

+ * + * @param index the index + * @return a double + */ + public double asDouble(int index) { + Tag tag = getIfExists(index); + if (tag instanceof ByteTag) { + return ((ByteTag) tag).getValue(); + + } else if (tag instanceof ShortTag) { + return ((ShortTag) tag).getValue(); + + } else if (tag instanceof IntTag) { + return ((IntTag) tag).getValue(); + + } else if (tag instanceof LongTag) { + return ((LongTag) tag).getValue(); + + } else if (tag instanceof FloatTag) { + return ((FloatTag) tag).getValue(); + + } else if (tag instanceof DoubleTag) { + return ((DoubleTag) tag).getValue(); + + } else { + return 0; + } + } + + /** + * Get a float named with the given index. + * + *

If the index does not exist or its value is not a float tag, + * then {@code 0} will be returned.

+ * + * @param index the index + * @return a float + */ + public float getFloat(int index) { + Tag tag = getIfExists(index); + if (tag instanceof FloatTag) { + return ((FloatTag) tag).getValue(); + } else { + return 0; + } + } + + /** + * Get a {@code int[]} named with the given index. + * + *

If the index does not exist or its value is not an int array tag, + * then an empty array will be returned.

+ * + * @param index the index + * @return an int array + */ + public int[] getIntArray(int index) { + Tag tag = getIfExists(index); + if (tag instanceof IntArrayTag) { + return ((IntArrayTag) tag).getValue(); + } else { + return new int[0]; + } + } + + /** + * Get an int named with the given index. + * + *

If the index does not exist or its value is not an int tag, + * then {@code 0} will be returned.

+ * + * @param index the index + * @return an int + */ + public int getInt(int index) { + Tag tag = getIfExists(index); + if (tag instanceof IntTag) { + return ((IntTag) tag).getValue(); + } else { + return 0; + } + } + + /** + * Get an int named with the given index, even if it's another + * type of number. + * + *

If the index does not exist or its value is not a number, + * then {@code 0} will be returned.

+ * + * @param index the index + * @return an int + */ + public int asInt(int index) { + Tag tag = getIfExists(index); + if (tag instanceof ByteTag) { + return ((ByteTag) tag).getValue(); + + } else if (tag instanceof ShortTag) { + return ((ShortTag) tag).getValue(); + + } else if (tag instanceof IntTag) { + return ((IntTag) tag).getValue(); + + } else if (tag instanceof LongTag) { + return ((LongTag) tag).getValue().intValue(); + + } else if (tag instanceof FloatTag) { + return ((FloatTag) tag).getValue().intValue(); + + } else if (tag instanceof DoubleTag) { + return ((DoubleTag) tag).getValue().intValue(); + + } else { + return 0; + } + } + + /** + * Get a list of tags named with the given index. + * + *

If the index does not exist or its value is not a list tag, + * then an empty list will be returned.

+ * + * @param index the index + * @return a list of tags + */ + public List getList(int index) { + Tag tag = getIfExists(index); + if (tag instanceof ListTag) { + return ((ListTag) tag).getValue(); + } else { + return Collections.emptyList(); + } + } + + /** + * Get a {@code TagList} named with the given index. + * + *

If the index does not exist or its value is not a list tag, + * then an empty tag list will be returned.

+ * + * @param index the index + * @return a tag list instance + */ + public ListTag getListTag(int index) { + Tag tag = getIfExists(index); + if (tag instanceof ListTag) { + return (ListTag) tag; + } else { + return new ListTag(StringTag.class, Collections.emptyList()); + } + } + + /** + * Get a list of tags named with the given index. + * + *

If the index does not exist or its value is not a list tag, + * then an empty list will be returned. If the given index references + * a list but the list of of a different type, then an empty + * list will also be returned.

+ * + * @param index the index + * @param listType the class of the contained type + * @return a list of tags + * @param the NBT type + */ + @SuppressWarnings("unchecked") + public List getList(int index, Class listType) { + Tag tag = getIfExists(index); + if (tag instanceof ListTag) { + ListTag listTag = (ListTag) tag; + if (listTag.getType().equals(listType)) { + return (List) listTag.getValue(); + } else { + return Collections.emptyList(); + } + } else { + return Collections.emptyList(); + } + } + + /** + * Get a long named with the given index. + * + *

If the index does not exist or its value is not a long tag, + * then {@code 0} will be returned.

+ * + * @param index the index + * @return a long + */ + public long getLong(int index) { + Tag tag = getIfExists(index); + if (tag instanceof LongTag) { + return ((LongTag) tag).getValue(); + } else { + return 0L; + } + } + + /** + * Get a long named with the given index, even if it's another + * type of number. + * + *

If the index does not exist or its value is not a number, + * then {@code 0} will be returned.

+ * + * @param index the index + * @return a long + */ + public long asLong(int index) { + Tag tag = getIfExists(index); + if (tag instanceof ByteTag) { + return ((ByteTag) tag).getValue(); + + } else if (tag instanceof ShortTag) { + return ((ShortTag) tag).getValue(); + + } else if (tag instanceof IntTag) { + return ((IntTag) tag).getValue(); + + } else if (tag instanceof LongTag) { + return ((LongTag) tag).getValue(); + + } else if (tag instanceof FloatTag) { + return ((FloatTag) tag).getValue().longValue(); + + } else if (tag instanceof DoubleTag) { + return ((DoubleTag) tag).getValue().longValue(); + + } else { + return 0; + } + } + + /** + * Get a short named with the given index. + * + *

If the index does not exist or its value is not a short tag, + * then {@code 0} will be returned.

+ * + * @param index the index + * @return a short + */ + public short getShort(int index) { + Tag tag = getIfExists(index); + if (tag instanceof ShortTag) { + return ((ShortTag) tag).getValue(); + } else { + return 0; + } + } + + /** + * Get a string named with the given index. + * + *

If the index does not exist or its value is not a string tag, + * then {@code ""} will be returned.

+ * + * @param index the index + * @return a string + */ + public String getString(int index) { + Tag tag = getIfExists(index); + if (tag instanceof StringTag) { + return ((StringTag) tag).getValue(); + } else { + return ""; + } + } + + @Override + public String toString() { + StringBuilder bldr = new StringBuilder(); + bldr.append("TAG_List").append(": ").append(value.size()).append(" entries of type ").append(NBTUtils.getTypeName(type)).append("\r\n{\r\n"); + for (Tag t : value) { + bldr.append(" ").append(t.toString().replaceAll("\r\n", "\r\n ")).append("\r\n"); + } + bldr.append("}"); + return bldr.toString(); + } + +} \ No newline at end of file diff --git a/core/src/main/java/com/sk89q/jnbt/Tag.java b/core/src/main/java/com/sk89q/jnbt/Tag.java new file mode 100644 index 00000000..35af4529 --- /dev/null +++ b/core/src/main/java/com/sk89q/jnbt/Tag.java @@ -0,0 +1,38 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.sk89q.jnbt; + +/** + * Represents a NBT tag. + */ +public abstract class Tag { + + /** + * Gets the value of this tag. + * + * @return the value + */ + public abstract Object getValue(); + + public Object getRaw() { + return getValue(); + } + +} \ No newline at end of file diff --git a/nukkit/lib/leveldb.jar b/nukkit/lib/leveldb.jar index 3322b6a3..2fbdc67b 100644 Binary files a/nukkit/lib/leveldb.jar and b/nukkit/lib/leveldb.jar differ diff --git a/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/ConverterFrame.java b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/ConverterFrame.java index b60a500d..53acb081 100644 --- a/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/ConverterFrame.java +++ b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/ConverterFrame.java @@ -11,6 +11,8 @@ import com.boydti.fawe.installer.MinimizeButton; import com.boydti.fawe.installer.MovablePanel; import com.boydti.fawe.installer.TextAreaOutputStream; import com.boydti.fawe.installer.URLButton; +import com.boydti.fawe.object.clipboard.ClipboardRemapper; +import com.boydti.fawe.object.clipboard.ItemWikiScraper; import com.boydti.fawe.util.MainUtil; import com.boydti.fawe.wrappers.FakePlayer; import java.awt.BorderLayout; @@ -30,6 +32,10 @@ import java.net.URL; import java.util.Arrays; import java.util.Date; import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import javax.imageio.ImageIO; import javax.swing.BorderFactory; import javax.swing.JButton; @@ -37,13 +43,17 @@ import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; +import javax.swing.JProgressBar; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.SwingConstants; +import javax.swing.border.Border; import javax.swing.border.EmptyBorder; +import javax.swing.border.TitledBorder; public class ConverterFrame extends JFrame { private final InvisiblePanel loggerPanel; + private final JProgressBar progressBar; private Color LIGHT_GRAY = new Color(0x66, 0x66, 0x66); private Color GRAY = new Color(0x44, 0x44, 0x46); private Color DARK_GRAY = new Color(0x33, 0x33, 0x36); @@ -56,6 +66,8 @@ public class ConverterFrame extends JFrame { private BrowseButton browseSave; public ConverterFrame() throws Exception { + async(() -> downloadDependencies()); + final MovablePanel movable = new MovablePanel(this); movable.setBorder(BorderFactory.createLineBorder(new Color(0x28, 0x28, 0x29))); @@ -69,23 +81,27 @@ public class ConverterFrame extends JFrame { int y = (int) ((dimension.getHeight() - this.getHeight()) / 2); this.setLocation(x, y); this.setVisible(true); - this.setOpacity(0); + + if (this.transparency(0)) { + fadeIn(); + } + movable.setBackground(DARK_GRAY); movable.setLayout(new BorderLayout()); - fadeIn(); - JPanel topBar = new InvisiblePanel(new BorderLayout()); { JPanel topBarLeft = new InvisiblePanel(); JPanel topBarCenter = new InvisiblePanel(); JPanel topBarRight = new InvisiblePanel(); - JLabel title = new JLabel("(FAWE) Anvil and LevelDB converter"); + JLabel title = new JLabel(); title.setHorizontalAlignment(SwingConstants.CENTER); title.setAlignmentX(Component.RIGHT_ALIGNMENT); title.setForeground(Color.LIGHT_GRAY); + title.setText("(FAWE) Anvil and LevelDB converter"); title.setFont(new Font("Lucida Sans Unicode", Font.PLAIN, 15)); + setTitle(null); MinimizeButton minimize = new MinimizeButton(this); CloseButton exit = new CloseButton(); @@ -195,8 +211,7 @@ public class ConverterFrame extends JFrame { installContent.add(install); installContent.setBorder(new EmptyBorder(10, 0, 10, 0)); this.loggerPanel = new InvisiblePanel(new BorderLayout()); - this.loggerPanel.setBackground(Color.GREEN); - loggerPanel.setPreferredSize(new Dimension(416, 442)); + loggerPanel.setPreferredSize(new Dimension(Integer.MAX_VALUE, 380)); loggerTextArea = new JTextArea(); loggerTextArea.setBackground(Color.GRAY); loggerTextArea.setForeground(Color.DARK_GRAY); @@ -208,10 +223,22 @@ public class ConverterFrame extends JFrame { loggerPanel.add(scroll); loggerPanel.setVisible(false); + this.progressBar = new JProgressBar(); + progressBar.setVisible(false); + progressBar.setStringPainted(true); + progressBar.setBackground(DARK_GRAY); + progressBar.setForeground(OFF_WHITE); + mainContent.setBorder(new EmptyBorder(6, 32, 6, 32)); mainContent.add(browseContent, BorderLayout.NORTH); mainContent.add(installContent, BorderLayout.CENTER); - mainContent.add(loggerPanel, BorderLayout.SOUTH); + + final JPanel console = new InvisiblePanel(new BorderLayout()); + console.setBorder(new EmptyBorder(6, 0, 6, 0)); + console.add(progressBar, BorderLayout.SOUTH); + console.add(loggerPanel, BorderLayout.NORTH); + + mainContent.add(console, BorderLayout.SOUTH); } JPanel bottomBar = new InvisiblePanel(); { @@ -270,6 +297,47 @@ public class ConverterFrame extends JFrame { this.repaint(); } + public void prompt(String message) { + JOptionPane.showMessageDialog(null, message); + Fawe.debug(message); + } + + public void debug(String m) { + System.out.println(m); + } + + public void setProgress(String text, int percent) { + Border border = BorderFactory.createTitledBorder(new EmptyBorder(0, 0, 0, 0), "Time remaining: " + text, TitledBorder.DEFAULT_JUSTIFICATION, TitledBorder.DEFAULT_POSITION, new Font("Lucida Sans Unicode",Font.PLAIN,12), OFF_WHITE); + progressBar.setVisible(true); + progressBar.setBorder(border); + progressBar.setValue(percent); + repaint(); + } + + private void fadeIn() { + async(() -> { + for (float i = 0; i <= 1.015; i += 0.016) { + if (!transparency(Math.min(1, i))) return; + try { + Thread.sleep(16); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (UnsupportedOperationException ignore) { + return; + } + } + }); + } + + private boolean transparency(float val) { + try { + super.setOpacity(val); + return true; + } catch (UnsupportedOperationException ignore) { + return false; + } + } + private File getDefaultOutput() { if (MainUtil.getPlatform() == MainUtil.OS.WINDOWS) { String applicationData = System.getenv("APPDATA"); @@ -281,16 +349,61 @@ public class ConverterFrame extends JFrame { return new File("."); } - public void prompt(String message) { - JOptionPane.showMessageDialog(null, message); - Fawe.debug(message); + private void async(Runnable r) { + new Thread(r).start(); } - public void debug(String m) { - System.out.println(m); + private AtomicBoolean dependenciesLoaded = new AtomicBoolean(false); + private void downloadDependencies() { + synchronized (dependenciesLoaded) { + if (dependenciesLoaded.get()) return; + try { + ExecutorService pool = Executors.newCachedThreadPool(); + ItemWikiScraper scraper = new ItemWikiScraper(); + + File lib = new File("lib"); + File leveldb = new File(lib, "leveldb_v1.jar"); + URL levelDbUrl = new URL("https://git.io/vdZ9e"); + + pool.submit((Runnable) () -> { + try { + MainUtil.download(levelDbUrl, leveldb); + MainUtil.loadURLClasspath(leveldb.toURL()); + } catch (IOException e) { + e.printStackTrace(); + } + }); + + pool.submit((Runnable) () -> { + try { + scraper.scapeOrCache(ClipboardRemapper.RemapPlatform.PE); + } catch (IOException e) { + e.printStackTrace(); + } + }); + + pool.submit((Runnable) () -> { + try { + scraper.scapeOrCache(ClipboardRemapper.RemapPlatform.PC); + } catch (IOException e) { + e.printStackTrace(); + } + }); + + pool.shutdown(); + try { + pool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + dependenciesLoaded.set(true); + } catch (IOException e) { + e.printStackTrace(); + } + } } - public void install(String input, String output) throws Exception { + private void install(String input, String output) throws Exception { if (!loggerPanel.isVisible()) { loggerPanel.setVisible(true); this.repaint(); @@ -324,30 +437,10 @@ public class ConverterFrame extends JFrame { public void run() { FakePlayer console = FakePlayer.getConsole(); try { - debug("Loading leveldb.jar"); - - File lib = new File("lib"); - - File leveldb = new File(lib, "leveldb.jar"); - URL levelDbUrl = new URL("https://git.io/vdZ9e"); - - -// File blocksPE = new File(lib, "blocks-pe.json"); -// File blocksPC = new File(lib, "blocks-pc.json"); - -// URL urlPE = new URL("https://git.io/vdZSj"); -// URL urlPC = new URL("https://git.io/vdZSx"); - - MainUtil.download(levelDbUrl, leveldb); -// MainUtil.download(urlPE, blocksPC); -// MainUtil.download(urlPC, blocksPE); - - MainUtil.loadURLClasspath(leveldb.toURL()); - - File newWorldFile = new File(output, dirMc.getName()); - + debug("Downloading levedb.jar and mappings (~4MB), please wait..."); + downloadDependencies(); debug("Starting converter..."); - + File newWorldFile = new File(output, dirMc.getName()); MapConverter converter = MapConverter.get(dirMc, newWorldFile); converter.accept(ConverterFrame.this); } catch (Throwable e) { @@ -355,28 +448,13 @@ public class ConverterFrame extends JFrame { prompt("[ERROR] Conversion failed, you will have to do it manually (Nukkit server + anvil2leveldb command)"); return; } + System.gc(); + System.gc(); } }); installThread.start(); } - public void fadeIn() { - Thread thread = new Thread(new Runnable() { - @Override - public void run() { - for (float i = 0; i <= 1.015; i += 0.016) { - ConverterFrame.this.setOpacity(Math.min(1, i)); - try { - Thread.sleep(16); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } - }); - thread.start(); - } - public static void main(String[] args) throws Exception { ConverterFrame window = new ConverterFrame(); } diff --git a/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/MCAFile2LevelDB.java b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/MCAFile2LevelDB.java index d905ebd5..3356d8fe 100644 --- a/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/MCAFile2LevelDB.java +++ b/nukkit/src/main/java/com/boydti/fawe/nukkit/core/converter/MCAFile2LevelDB.java @@ -12,6 +12,7 @@ import com.boydti.fawe.object.RunnableVal; import com.boydti.fawe.object.clipboard.ClipboardRemapper; import com.boydti.fawe.object.io.LittleEndianOutputStream; import com.boydti.fawe.object.number.MutableLong; +import com.boydti.fawe.util.MainUtil; import com.boydti.fawe.util.MemUtil; import com.boydti.fawe.util.ReflectionUtils; import com.boydti.fawe.util.StringMan; @@ -52,6 +53,7 @@ import java.util.concurrent.ForkJoinPool; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.LongAdder; import java.util.zip.GZIPInputStream; +import org.iq80.leveldb.CompressionType; import org.iq80.leveldb.DB; import org.iq80.leveldb.Options; import org.iq80.leveldb.WriteBatch; @@ -61,16 +63,22 @@ public class MCAFile2LevelDB extends MapConverter { private final byte[] VERSION = new byte[] { 4 }; private final byte[] COMPLETE_STATE = new byte[] { 2, 0, 0, 0 }; - private DB db; + private final DB db; private final ClipboardRemapper remapper; private final ForkJoinPool pool; private boolean closed; - private LongAdder submitted = new LongAdder(); + private LongAdder submittedChunks = new LongAdder(); + private LongAdder submittedFiles = new LongAdder(); + + private LongAdder totalOperations = new LongAdder(); + private long estimatedOperations; private long time; - private boolean remap; + private final long startTime; + private ConverterFrame app; + private ConcurrentLinkedQueue portals = new ConcurrentLinkedQueue<>(); private ConcurrentHashMap batches = new ConcurrentHashMap() { @@ -79,7 +87,7 @@ public class MCAFile2LevelDB extends MapConverter { public WriteBatch get(Object key) { WriteBatch value = super.get(key); if (value == null) { - synchronized (batches) { + synchronized (MCAFile2LevelDB.this) { synchronized (Thread.currentThread()) { value = db.createWriteBatch(); put((Thread) key, value); @@ -92,6 +100,7 @@ public class MCAFile2LevelDB extends MapConverter { public MCAFile2LevelDB(File folderFrom, File folderTo) { super(folderFrom, folderTo); + this.startTime = System.currentTimeMillis(); try { if (!folderTo.exists()) { folderTo.mkdirs(); @@ -104,15 +113,61 @@ public class MCAFile2LevelDB extends MapConverter { this.pool = new ForkJoinPool(); this.remapper = new ClipboardRemapper(ClipboardRemapper.RemapPlatform.PC, ClipboardRemapper.RemapPlatform.PE); BundledBlockData.getInstance().loadFromResource(); - flush(true); + + int bufferSize = (int) Math.min(Integer.MAX_VALUE, Math.max((long) (MemUtil.getFreeBytes() * 0.8), 134217728)); + this.db = Iq80DBFactory.factory.open(new File(folderTo, "db"), + new Options() + .createIfMissing(true) + .verifyChecksums(false) + .compressionType(CompressionType.ZLIB) + .blockSize(262144) // 256K + .cacheSize(8388608) // 8MB + .writeBufferSize(134217728) // >=512MB + ); + } catch (IOException e) { throw new RuntimeException(e); } } - public void flush(boolean openDB) throws IOException { + private double lastPercent; + private double lastTimeRatio; + + private void progress(int increment) { + if (app == null) return; + totalOperations.add(increment); + + long completedOps = totalOperations.longValue(); + double percent = Math.max(0, Math.min(100, (Math.pow(completedOps, 1.5) * 100 / Math.pow(estimatedOperations, 1.5)))); + double lastPercent = (this.lastPercent == 0 ? percent : this.lastPercent); + percent = (percent + lastPercent * 19) / 20; + if (increment != 0) this.lastPercent = percent; + + double remaining = estimatedOperations - completedOps; + long timeSpent = System.currentTimeMillis() - startTime; + + double totalTime = Math.pow(estimatedOperations, 1.5) * 1000; + double estimatedTimeSpent = Math.pow(completedOps, 1.5) * 1000; + + double timeRemaining; + if (completedOps > 32) { + double timeRatio = (timeSpent * totalTime / estimatedTimeSpent); + double lastTimeRatio = this.lastTimeRatio == 0 ? timeRatio : this.lastTimeRatio; + timeRatio = (timeRatio + lastTimeRatio * 19) / 20; + if (increment != 0) this.lastTimeRatio = timeRatio; + timeRemaining = timeRatio - timeSpent; + } else { + timeRemaining = ((long) totalTime >> 5) - timeSpent; + } + String msg = MainUtil.secToTime((long) (timeRemaining / 1000)); + app.setProgress(msg, (int) percent); + } + + public synchronized void flush() throws IOException { pool.awaitQuiescence(Long.MAX_VALUE, TimeUnit.MILLISECONDS); - synchronized (batches) { + progress(1); + synchronized (MCAFile2LevelDB.this) { + int count = pool.getParallelism(); Iterator> iter = batches.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); @@ -121,29 +176,20 @@ public class MCAFile2LevelDB extends MapConverter { db.write(batch); batch.close(); iter.remove(); + progress(2); + count--; } } - if (openDB) { - Fawe.debug("Flushing changes, please wait"); - if (db != null) db.close(); - System.gc(); - System.gc(); - int bufferSize = (int) Math.min(Integer.MAX_VALUE, Math.max((long) (MemUtil.getFreeBytes() * 0.8), 134217728)); - this.db = Iq80DBFactory.factory.open(new File(folderTo, "db"), - new Options() - .createIfMissing(true) - .verifyChecksums(false) - .blockSize(262144) // 256K - .cacheSize(8388608) // 8MB - .writeBufferSize(536870912) // >=512MB - ); - } + System.gc(); + System.gc(); + progress(count * 2); } } public DelegateMCAFilter toFilter(final int dimension) { RemapFilter filter = new RemapFilter(ClipboardRemapper.RemapPlatform.PC, ClipboardRemapper.RemapPlatform.PE); filter.setDimension(dimension); + DelegateMCAFilter delegate = new DelegateMCAFilter(filter) { @Override public void finishFile(MCAFile file, MutableLong cache) { @@ -158,6 +204,16 @@ public class MCAFile2LevelDB extends MapConverter { } }); file.clear(); + + progress(1); + submittedFiles.increment(); + if ((submittedFiles.longValue() & 7) == 0) { + try { + flush(); + } catch (IOException e) { + e.printStackTrace(); + } + } } }; return delegate; @@ -166,6 +222,7 @@ public class MCAFile2LevelDB extends MapConverter { @Override public void accept(ConverterFrame app) { + this.app = app; File levelDat = new File(folderFrom, "level.dat"); if (levelDat.exists()) { try { @@ -177,12 +234,34 @@ public class MCAFile2LevelDB extends MapConverter { List portals = new ArrayList<>(); String[] dimDirs = {"region", "DIM-1/region", "DIM1/region"}; + File[] regionFolders = new File[dimDirs.length]; + int totalFiles = 0; for (int dim = 0; dim < 3; dim++) { File source = new File(folderFrom, dimDirs[dim]); if (source.exists()) { + regionFolders[dim] = source; + totalFiles += source.listFiles().length; + } + } + this.estimatedOperations = totalFiles + (totalFiles >> 3) + (totalFiles >> 3) * pool.getParallelism() * 2; + + Thread progressThread = new Thread(() -> { + while (!Thread.currentThread().isInterrupted()) { + try { + Thread.sleep(1000); + progress(0); + } catch (InterruptedException e) { + return; + } + } + }); + progressThread.start(); + + for (int dim = 0; dim < regionFolders.length; dim++) { + File source = regionFolders[dim]; + if (source != null) { DelegateMCAFilter filter = toFilter(dim); MCAQueue queue = new MCAQueue(null, source, true); - MCAFilter result = queue.filterWorld(filter); portals.addAll(((RemapFilter) filter.getFilter()).getPortals()); } @@ -201,11 +280,13 @@ public class MCAFile2LevelDB extends MapConverter { try { - flush(false); + flush(); } catch (IOException e) { e.printStackTrace(); } + progressThread.interrupt(); close(); + app.setProgress("Done", 0); app.prompt( "Conversion complete!\n" + " - The world save is still being compacted, but you can close the program anytime\n" + @@ -223,7 +304,7 @@ public class MCAFile2LevelDB extends MapConverter { } @Override - public void close() { + public synchronized void close() { try { if (closed == (closed = true)) return; Fawe.debug("Collecting threads"); @@ -238,7 +319,7 @@ public class MCAFile2LevelDB extends MapConverter { } } - public void compact() { + public synchronized void compact() { // Since the library doesn't support it, only way to flush the cache is to loop over everything try (DB newDb = Iq80DBFactory.factory.open(new File(folderTo, "db"), new Options() .verifyChecksums(false) @@ -315,8 +396,9 @@ public class MCAFile2LevelDB extends MapConverter { } public void write(MCAChunk chunk, boolean remap, int dim) throws IOException { - submitted.add(1); - if ((submitted.longValue() & 1023) == 0) { + submittedChunks.add(1); + long numChunks = submittedChunks.longValue(); + if ((numChunks & 1023) == 0) { long queued = pool.getQueuedTaskCount() + pool.getQueuedSubmissionCount(); if (queued > 127) { System.gc(); @@ -329,110 +411,106 @@ public class MCAFile2LevelDB extends MapConverter { } } } - if ((submitted.longValue() & 8191) == 0) { - boolean reopen = (submitted.longValue() & 65535) == 0; - flush(reopen); + } + synchronized (MCAFile2LevelDB.this) { + try { + update(getKey(chunk, Tag.Version, dim), VERSION); + update(getKey(chunk, Tag.FinalizedState, dim), COMPLETE_STATE); + + ByteBuffer data2d = ByteBuffer.wrap(new byte[512 + 256]); + int[] heightMap = chunk.getHeightMapArray(); + for (int i = 0; i < heightMap.length; i++) { + data2d.putShort((short) heightMap[i]); + } + if (chunk.biomes != null) { + System.arraycopy(chunk.biomes, 0, data2d.array(), 512, 256); + } + update(getKey(chunk, Tag.Data2D, dim), data2d.array()); + + if (!chunk.tiles.isEmpty()) { + List tickList = null; + List tiles = new ArrayList<>(); + for (Map.Entry entry : chunk.getTiles().entrySet()) { + CompoundTag tag = entry.getValue(); + if (transform(chunk, tag) && time != 0l) { + // Needs tick + if (tickList == null) tickList = new ArrayList<>(); + + int x = tag.getInt("x"); + int y = tag.getInt("y"); + int z = tag.getInt("z"); + BaseBlock block = chunk.getBlock(x & 15, y, z & 15); + + Map tickable = new HashMap<>(); + tickable.put("tileID", new ByteTag((byte) block.getId())); + tickable.put("x", new IntTag(x)); + tickable.put("y", new IntTag(y)); + tickable.put("z", new IntTag(z)); + tickable.put("time", new LongTag(1)); + tickList.add(new CompoundTag(tickable)); + } + + tiles.add(tag); + } + update(getKey(chunk, Tag.BlockEntity, dim), write(tiles)); + + if (tickList != null) { + HashMap root = new HashMap(); + root.put("tickList", new ListTag(CompoundTag.class, tickList)); + update(getKey(chunk, Tag.PendingTicks, dim), write(Arrays.asList(new CompoundTag(root)))); + } + } + + if (!chunk.entities.isEmpty()) { + List entities = new ArrayList<>(); + for (CompoundTag tag : chunk.getEntities()) { + transform(chunk, tag); + entities.add(tag); + } + update(getKey(chunk, Tag.Entity, dim), write(entities)); + } + + int maxLayer = chunk.ids.length - 1; + while (maxLayer >= 0 && chunk.ids[maxLayer] == null) maxLayer--; + if (maxLayer >= 0) { + for (int layer = 0; layer <= maxLayer; layer++) { + // Set layer + byte[] key = getSectionKey(chunk, layer, dim); + byte[] value = new byte[1 + 4096 + 2048 + 2048 + 2048]; + byte[] ids = chunk.ids[layer]; + if (ids == null) { + Arrays.fill(value, (byte) 0); + } else { + byte[] data = chunk.data[layer]; + byte[] skyLight = chunk.skyLight[layer]; + byte[] blockLight = chunk.blockLight[layer]; + + if (remap) { + copySection(ids, value, 1); + copySection(data, value, 1 + 4096); + copySection(skyLight, value, 1 + 4096 + 2048); + copySection(blockLight, value, 1 + 4096 + 2048 + 2048); + } else { + System.arraycopy(ids, 0, value, 1, ids.length); + System.arraycopy(data, 0, value, 1 + 4096, data.length); + System.arraycopy(skyLight, 0, value, 1 + 4096 + 2048, skyLight.length); + System.arraycopy(blockLight, 0, value, 1 + 4096 + 2048 + 2048, blockLight.length); + } + } + update(key, value); + } + } + } catch (Throwable e) { + e.printStackTrace(); } } - pool.submit((Runnable) () -> { - synchronized (Thread.currentThread()) { - try { - update(getKey(chunk, Tag.Version, dim), VERSION); - update(getKey(chunk, Tag.FinalizedState, dim), COMPLETE_STATE); - - ByteBuffer data2d = ByteBuffer.wrap(new byte[512 + 256]); - int[] heightMap = chunk.getHeightMapArray(); - for (int i = 0; i < heightMap.length; i++) { - data2d.putShort((short) heightMap[i]); - } - if (chunk.biomes != null) { - System.arraycopy(chunk.biomes, 0, data2d.array(), 512, 256); - } - update(getKey(chunk, Tag.Data2D, dim), data2d.array()); - - if (!chunk.tiles.isEmpty()) { - List tickList = null; - List tiles = new ArrayList<>(); - for (Map.Entry entry : chunk.getTiles().entrySet()) { - CompoundTag tag = entry.getValue(); - if (transform(chunk, tag) && time != 0l) { - // Needs tick - if (tickList == null) tickList = new ArrayList<>(); - - int x = tag.getInt("x"); - int y = tag.getInt("y"); - int z = tag.getInt("z"); - BaseBlock block = chunk.getBlock(x & 15, y, z & 15); - - Map tickable = new HashMap<>(); - tickable.put("tileID", new ByteTag((byte) block.getId())); - tickable.put("x", new IntTag(x)); - tickable.put("y", new IntTag(y)); - tickable.put("z", new IntTag(z)); - tickable.put("time", new LongTag(1)); - tickList.add(new CompoundTag(tickable)); - } - - tiles.add(tag); - } - update(getKey(chunk, Tag.BlockEntity, dim), write(tiles)); - - if (tickList != null) { - HashMap root = new HashMap(); - root.put("tickList", new ListTag(CompoundTag.class, tickList)); - update(getKey(chunk, Tag.PendingTicks, dim), write(Arrays.asList(new CompoundTag(root)))); - } - } - - if (!chunk.entities.isEmpty()) { - List entities = new ArrayList<>(); - for (CompoundTag tag : chunk.getEntities()) { - transform(chunk, tag); - entities.add(tag); - } - update(getKey(chunk, Tag.Entity, dim), write(entities)); - } - - int maxLayer = chunk.ids.length - 1; - while (maxLayer >= 0 && chunk.ids[maxLayer] == null) maxLayer--; - if (maxLayer >= 0) { - for (int layer = maxLayer; layer >= 0; layer--) { - // Set layer - byte[] key = getSectionKey(chunk, layer, dim); - byte[] value = new byte[1 + 4096 + 2048 + 2048 + 2048]; - byte[] ids = chunk.ids[layer]; - if (ids == null) { - Arrays.fill(value, (byte) 0); - } else { - byte[] data = chunk.data[layer]; - byte[] skyLight = chunk.skyLight[layer]; - byte[] blockLight = chunk.blockLight[layer]; - - if (remap) { - copySection(ids, value, 1); - copySection(data, value, 1 + 4096); - copySection(skyLight, value, 1 + 4096 + 2048); - copySection(blockLight, value, 1 + 4096 + 2048 + 2048); - } else { - System.arraycopy(ids, 0, value, 1, ids.length); - System.arraycopy(data, 0, value, 1 + 4096, data.length); - System.arraycopy(skyLight, 0, value, 1 + 4096 + 2048, skyLight.length); - System.arraycopy(blockLight, 0, value, 1 + 4096 + 2048 + 2048, blockLight.length); - } - } - update(key, value); - } - } - } catch (Throwable e) { - e.printStackTrace(); - } - } - }); } private void update(byte[] key, byte[] value) { - WriteBatch batch = batches.get(Thread.currentThread()); - batch.put(key, value); + synchronized (Thread.currentThread()) { + WriteBatch batch = batches.get(Thread.currentThread()); + batch.put(key, value); + } } private void copySection(byte[] src, byte[] dest, int destPos) { @@ -541,6 +619,9 @@ public class MCAFile2LevelDB extends MapConverter { Map map = ReflectionUtils.getMap(tag.getValue()); id = remapper.remapEntityId(id); map.put("id", new StringTag(id)); + if (map.containsKey("Pos")) { + map.put("id", new IntTag(11)); + } { // Convert items com.sk89q.jnbt.ListTag items = tag.getListTag("Items"); ((List) (List) items.getValue()).forEach(this::transformItem); @@ -555,13 +636,21 @@ public class MCAFile2LevelDB extends MapConverter { } } { // Health - com.sk89q.jnbt.Tag health = map.get("Health"); - if (health != null && health instanceof FloatTag) { - map.put("Health", new ShortTag((short) tag.getFloat("Health"))); + com.sk89q.jnbt.Tag tVal = map.get("Health"); + if (tVal != null) { + short newVal = ((Number) tVal.getValue()).shortValue(); + map.put("Health", new ShortTag((short) (newVal * 2))); + } + } + for (String key : new String[] {"Age", "Health"}) { + com.sk89q.jnbt.Tag tVal = map.get(key); + if (tVal != null) { + short newVal = ((Number) tVal.getValue()).shortValue(); + map.put(key, new ShortTag(newVal)); } } { // Orientation / Position - for (String key : new String[] {"Orientation", "Position"}) { + for (String key : new String[] {"Orientation", "Position", "Rotation", "Pos", "Motion"}) { ListTag list = (ListTag) map.get(key); if (list != null) { List value = list.getValue();