Implemented NBT Saving

1.13: CustomItemTagContainer
1.14: PersistentDataContainer
This commit is contained in:
Sn0wStorm
2019-10-20 21:06:35 +02:00
parent f431c13100
commit 4909e59c90
11 changed files with 222 additions and 29 deletions
+2 -2
View File
@@ -76,7 +76,7 @@
</repository> </repository>
<repository> <repository>
<id>spigot-repo</id> <id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/groups/public/</url> <url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository> </repository>
<repository> <repository>
<id>vault-repo</id> <id>vault-repo</id>
@@ -111,7 +111,7 @@
<dependency> <dependency>
<groupId>org.bukkit</groupId> <groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId> <artifactId>bukkit</artifactId>
<version>1.14-R0.1-SNAPSHOT</version> <version>1.14.4-R0.1-SNAPSHOT</version>
<scope>provided</scope> <scope>provided</scope>
<exclusions> <exclusions>
<exclusion> <exclusion>
+3 -1
View File
@@ -60,7 +60,9 @@ public class BDistiller {
final int fuel = standInv.getHolder().getFuelLevel(); final int fuel = standInv.getHolder().getFuelLevel();
// Now check if we should bother to track it. // Now check if we should bother to track it.
trackedDistillers.put(standBlock, new BDistiller(standBlock, fuel)).start(); distiller = new BDistiller(standBlock, fuel);
trackedDistillers.put(standBlock, distiller);
distiller.start();
} }
// Returns a Brew or null for every Slot in the BrewerInventory // Returns a Brew or null for every Slot in the BrewerInventory
+62 -16
View File
@@ -16,10 +16,7 @@ import org.bukkit.potion.PotionEffectType;
import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.io.DataInputStream; import java.io.*;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.ArrayList; import java.util.ArrayList;
@@ -44,6 +41,7 @@ public class Brew {
private boolean persistent; // Only for legacy private boolean persistent; // Only for legacy
private boolean immutable; // static/immutable potions should not be changed private boolean immutable; // static/immutable potions should not be changed
private int lastUpdate; // last update in hours after install time private int lastUpdate; // last update in hours after install time
private boolean needsSave; // There was a change that has not yet been saved
public Brew(BIngredients ingredients) { public Brew(BIngredients ingredients) {
this.ingredients = ingredients; this.ingredients = ingredients;
@@ -78,7 +76,7 @@ public class Brew {
// returns a Brew by ItemMeta // returns a Brew by ItemMeta
@Nullable @Nullable
public static Brew get(ItemMeta meta) { public static Brew get(ItemMeta meta) {
if (!meta.hasLore()) return null; if (!P.useNBT && !meta.hasLore()) return null;
Brew brew = load(meta); Brew brew = load(meta);
@@ -96,7 +94,7 @@ public class Brew {
if (!item.hasItemMeta()) return null; if (!item.hasItemMeta()) return null;
ItemMeta meta = item.getItemMeta(); ItemMeta meta = item.getItemMeta();
if (!meta.hasLore()) return null; if (!P.useNBT && !meta.hasLore()) return null;
Brew brew = load(meta); Brew brew = load(meta);
@@ -104,7 +102,16 @@ public class Brew {
// Load Legacy and convert // Load Legacy and convert
brew = getFromPotionEffect(((PotionMeta) meta), true); brew = getFromPotionEffect(((PotionMeta) meta), true);
if (brew == null) return null; if (brew == null) return null;
new BrewLore(brew, ((PotionMeta) meta)).removeLegacySpacing(); new BrewLore(brew, (PotionMeta) meta).removeLegacySpacing();
brew.save(meta);
item.setItemMeta(meta);
} else if (brew != null && brew.needsSave) {
// Brew needs saving from a previous format
P.p.debugLog("Brew needs saving from previous format");
if (P.useNBT) {
new BrewLore(brew, (PotionMeta) meta).removeLoreData();
P.p.debugLog("removed Data from Lore");
}
brew.save(meta); brew.save(meta);
item.setItemMeta(meta); item.setItemMeta(meta);
} }
@@ -434,6 +441,14 @@ public class Brew {
return unlabeled; return unlabeled;
} }
public boolean needsSave() {
return needsSave;
}
public void setNeedsSave(boolean needsSave) {
this.needsSave = needsSave;
}
// Set the Static flag, so potion is unchangeable // Set the Static flag, so potion is unchangeable
public void setStatic(boolean immutable, ItemStack potion) { public void setStatic(boolean immutable, ItemStack potion) {
this.immutable = immutable; this.immutable = immutable;
@@ -660,14 +675,25 @@ public class Brew {
} }
private static Brew load(ItemMeta meta) { private static Brew load(ItemMeta meta) {
LoreLoadStream loreStream; InputStream itemLoadStream = null;
try { if (P.useNBT) {
loreStream = new LoreLoadStream(meta, 0); // Try loading the Item Data from PersistentDataContainer
} catch (IllegalArgumentException ignored) { NBTLoadStream nbtStream = new NBTLoadStream(meta);
// No Brew data found in Meta if (nbtStream.hasData()) {
return null; itemLoadStream = nbtStream;
}
} }
XORUnscrambleStream unscrambler = new XORUnscrambleStream(new Base91DecoderStream(loreStream), saveSeed, prevSaveSeeds); if (itemLoadStream == null) {
// If either NBT is not supported or no data was found in NBT, try loading from Lore
try {
itemLoadStream = new Base91DecoderStream(new LoreLoadStream(meta, 0));
} catch (IllegalArgumentException ignored) {
// No Brew data found in Meta
return null;
}
}
XORUnscrambleStream unscrambler = new XORUnscrambleStream(itemLoadStream, saveSeed, prevSaveSeeds);
try (DataInputStream in = new DataInputStream(unscrambler)) { try (DataInputStream in = new DataInputStream(unscrambler)) {
boolean parityFailed = false; boolean parityFailed = false;
if (in.readByte() != 86) { if (in.readByte() != 86) {
@@ -678,8 +704,10 @@ public class Brew {
byte ver = in.readByte(); byte ver = in.readByte();
switch (ver) { switch (ver) {
case 1: case 1:
unscrambler.start(); unscrambler.start();
brew.loadFromStream(in); brew.loadFromStream(in);
break; break;
default: default:
if (parityFailed) { if (parityFailed) {
@@ -689,6 +717,18 @@ public class Brew {
} }
return null; return null;
} }
XORUnscrambleStream.SuccessType successType = unscrambler.getSuccessType();
if (successType == XORUnscrambleStream.SuccessType.PREV_SEED) {
brew.setNeedsSave(true);
} else if (BConfig.enableEncode != (successType == XORUnscrambleStream.SuccessType.MAIN_SEED)) {
// We have either enabled encode and the data was not encoded or the other way round
brew.setNeedsSave(true);
} else if (P.useNBT && itemLoadStream instanceof Base91DecoderStream) {
// We are on a version that supports nbt but the data is still in the lore of the item
// Just save it again so that it gets saved to nbt
brew.setNeedsSave(true);
}
return brew; return brew;
} catch (IOException e) { } catch (IOException e) {
P.p.errorLog("IO Error while loading Brew"); P.p.errorLog("IO Error while loading Brew");
@@ -723,9 +763,15 @@ public class Brew {
ingredients = BIngredients.load(in); ingredients = BIngredients.load(in);
} }
// Save brew data into meta/lore // Save brew data into meta: lore/nbt
public void save(ItemMeta meta) { public void save(ItemMeta meta) {
XORScrambleStream scrambler = new XORScrambleStream(new Base91EncoderStream(new LoreSaveStream(meta, 0)), saveSeed); OutputStream itemSaveStream;
if (P.useNBT) {
itemSaveStream = new NBTSaveStream(meta);
} else {
itemSaveStream = new Base91EncoderStream(new LoreSaveStream(meta, 0));
}
XORScrambleStream scrambler = new XORScrambleStream(itemSaveStream, saveSeed);
try (DataOutputStream out = new DataOutputStream(scrambler)) { try (DataOutputStream out = new DataOutputStream(scrambler)) {
out.writeByte(86); // Parity/sanity out.writeByte(86); // Parity/sanity
out.writeByte(1); // Version out.writeByte(1); // Version
+9
View File
@@ -9,6 +9,7 @@ import com.dre.brewery.integration.IntegrationListener;
import com.dre.brewery.integration.LogBlockBarrel; import com.dre.brewery.integration.LogBlockBarrel;
import com.dre.brewery.listeners.*; import com.dre.brewery.listeners.*;
import com.dre.brewery.utility.BUtil; import com.dre.brewery.utility.BUtil;
import com.dre.brewery.utility.LegacyUtil;
import org.apache.commons.lang.math.NumberUtils; import org.apache.commons.lang.math.NumberUtils;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
@@ -22,6 +23,7 @@ public class P extends JavaPlugin {
public static P p; public static P p;
public static boolean debug; public static boolean debug;
public static boolean useUUID; public static boolean useUUID;
public static boolean useNBT;
public static boolean use1_9; public static boolean use1_9;
public static boolean use1_11; public static boolean use1_11;
public static boolean use1_13; public static boolean use1_13;
@@ -51,6 +53,13 @@ public class P extends JavaPlugin {
use1_13 = !v.matches("(^|.*[^.\\d])1\\.1[0-2]([^\\d].*|$)") && !v.matches("(^|.*[^.\\d])1\\.[0-9]([^\\d].*|$)"); use1_13 = !v.matches("(^|.*[^.\\d])1\\.1[0-2]([^\\d].*|$)") && !v.matches("(^|.*[^.\\d])1\\.[0-9]([^\\d].*|$)");
use1_14 = !v.matches("(^|.*[^.\\d])1\\.1[0-3]([^\\d].*|$)") && !v.matches("(^|.*[^.\\d])1\\.[0-9]([^\\d].*|$)"); use1_14 = !v.matches("(^|.*[^.\\d])1\\.1[0-3]([^\\d].*|$)") && !v.matches("(^|.*[^.\\d])1\\.[0-9]([^\\d].*|$)");
//MC 1.13 uses a different NBT API than the newer versions..
// We decide here which to use, the new or the old or none at all
P.debug = true;
if (LegacyUtil.initNbt()) {
useNBT = true;
}
//P.p.log("§" + (use1_9 ? "a":"c") + "1.9 " + "§" + (use1_11 ? "a":"c") + "1.11 " + "§" + (use1_13 ? "a":"c") + "1.13 " + "§" + (use1_14 ? "a":"c") + "1.14"); //P.p.log("§" + (use1_9 ? "a":"c") + "1.9 " + "§" + (use1_11 ? "a":"c") + "1.11 " + "§" + (use1_13 ? "a":"c") + "1.13 " + "§" + (use1_14 ? "a":"c") + "1.14");
/*long master = new SecureRandom().nextLong(); /*long master = new SecureRandom().nextLong();
+12
View File
@@ -189,12 +189,24 @@ public class BrewLore {
} }
public void removeLegacySpacing() { public void removeLegacySpacing() {
if (P.useNBT) {
// Using NBT we don't get the invisible line, so we keep our spacing
return;
}
if (lore.size() > 0 && lore.get(0).equals("")) { if (lore.size() > 0 && lore.get(0).equals("")) {
lore.remove(0); lore.remove(0);
write(); write();
} }
} }
public void removeLoreData() {
int index = BUtil.indexOfStart(lore, LoreSaveStream.IDENTIFIER);
if (index != -1) {
lore.set(index, "");
write();
}
}
// True if the PotionMeta has Lore in quality color // True if the PotionMeta has Lore in quality color
public static boolean hasColorLore(PotionMeta meta) { public static boolean hasColorLore(PotionMeta meta) {
if (!meta.hasLore()) return false; if (!meta.hasLore()) return false;
+4 -2
View File
@@ -8,6 +8,8 @@ import java.util.List;
public class LoreLoadStream extends ByteArrayInputStream { public class LoreLoadStream extends ByteArrayInputStream {
public static final String IDENTIFIER = "§%";
public LoreLoadStream(ItemMeta meta) throws IllegalArgumentException { public LoreLoadStream(ItemMeta meta) throws IllegalArgumentException {
this(meta, -1); this(meta, -1);
} }
@@ -21,12 +23,12 @@ public class LoreLoadStream extends ByteArrayInputStream {
List<String> lore = meta.getLore(); List<String> lore = meta.getLore();
if (lineNum >= 0) { if (lineNum >= 0) {
String line = lore.get(lineNum); String line = lore.get(lineNum);
if (line.startsWith("§%")) { if (line.startsWith(IDENTIFIER)) {
return loreLineToBytes(line); return loreLineToBytes(line);
} }
} }
for (String line : lore) { for (String line : lore) {
if (line.startsWith("§%")) { if (line.startsWith(IDENTIFIER)) {
return loreLineToBytes(line); return loreLineToBytes(line);
} }
} }
+4 -2
View File
@@ -10,6 +10,8 @@ import java.util.List;
public class LoreSaveStream extends ByteArrayOutputStream { public class LoreSaveStream extends ByteArrayOutputStream {
public static final String IDENTIFIER = "§%";
private ItemMeta meta; private ItemMeta meta;
private int line; private int line;
private boolean flushed = false; private boolean flushed = false;
@@ -38,7 +40,7 @@ public class LoreSaveStream extends ByteArrayOutputStream {
String s = toString(); String s = toString();
StringBuilder loreLineBuilder = new StringBuilder((s.length() * 2) + 6); StringBuilder loreLineBuilder = new StringBuilder((s.length() * 2) + 6);
loreLineBuilder.append("§%"); loreLineBuilder.append(IDENTIFIER);
for (char c : s.toCharArray()) { for (char c : s.toCharArray()) {
loreLineBuilder.append('§').append(c); loreLineBuilder.append('§').append(c);
} }
@@ -50,7 +52,7 @@ public class LoreSaveStream extends ByteArrayOutputStream {
} }
int prev = 0; int prev = 0;
for (Iterator<String> iterator = lore.iterator(); iterator.hasNext(); ) { for (Iterator<String> iterator = lore.iterator(); iterator.hasNext(); ) {
if (iterator.next().startsWith("§%")) { if (iterator.next().startsWith(IDENTIFIER)) {
iterator.remove(); iterator.remove();
break; break;
} }
@@ -0,0 +1,28 @@
package com.dre.brewery.lore;
import com.dre.brewery.P;
import com.dre.brewery.utility.LegacyUtil;
import org.bukkit.NamespacedKey;
import org.bukkit.inventory.meta.ItemMeta;
import java.io.ByteArrayInputStream;
public class NBTLoadStream extends ByteArrayInputStream {
private static final String TAG = "brewdata";
public NBTLoadStream(ItemMeta meta) {
super(getNBTBytes(meta));
}
private static byte[] getNBTBytes(ItemMeta meta) {
byte[] bytes = LegacyUtil.readBytesItem(meta, new NamespacedKey(P.p, TAG));
if (bytes == null) {
return new byte[0];
}
return bytes;
}
public boolean hasData() {
return count > 0;
}
}
@@ -0,0 +1,26 @@
package com.dre.brewery.lore;
import com.dre.brewery.P;
import com.dre.brewery.utility.LegacyUtil;
import org.bukkit.NamespacedKey;
import org.bukkit.inventory.meta.ItemMeta;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class NBTSaveStream extends ByteArrayOutputStream {
private static final String TAG = "brewdata";
private final ItemMeta meta;
public NBTSaveStream(ItemMeta meta) {
super(128);
this.meta = meta;
}
@Override
public void flush() throws IOException {
super.flush();
if (size() <= 0) return;
LegacyUtil.writeBytesItem(toByteArray(), meta, new NamespacedKey(P.p, TAG));
}
}
@@ -23,6 +23,8 @@ public class XORUnscrambleStream extends FilterInputStream {
private boolean markRunning; private boolean markRunning;
private boolean markxor; private boolean markxor;
private SuccessType successType = SuccessType.NONE;
/** /**
* Create a new instance of an XORUnscrambler, unscrambling the given inputstream * Create a new instance of an XORUnscrambler, unscrambling the given inputstream
* *
@@ -62,19 +64,24 @@ public class XORUnscrambleStream extends FilterInputStream {
short id = (short) (in.read() << 8 | in.read()); short id = (short) (in.read() << 8 | in.read());
if (id == 0) { if (id == 0) {
running = false; running = false;
successType = SuccessType.UNSCRAMBLED;
P.p.debugLog("Unscrambled data"); P.p.debugLog("Unscrambled data");
return; return;
} }
int parity = in.read(); int parity = in.read();
xorStream = new SeedInputStream(seed ^ id); xorStream = new SeedInputStream(seed ^ id);
boolean success = checkParity(parity); boolean success = checkParity(parity);
if (success) P.p.debugLog("Using main Seed to unscramble"); if (success) {
successType = SuccessType.MAIN_SEED;
P.p.debugLog("Using main Seed to unscramble");
}
if (!success && prevSeeds != null) { if (!success && prevSeeds != null) {
for (int i = prevSeeds.size() - 1; i >= 0; i--) { for (int i = prevSeeds.size() - 1; i >= 0; i--) {
seed = prevSeeds.get(i); seed = prevSeeds.get(i);
xorStream = new SeedInputStream(seed ^ id); xorStream = new SeedInputStream(seed ^ id);
if (success = checkParity(parity)) { if (success = checkParity(parity)) {
successType = SuccessType.PREV_SEED;
P.p.debugLog("Had to use prevSeed to unscramble"); P.p.debugLog("Had to use prevSeed to unscramble");
break; break;
} }
@@ -118,6 +125,15 @@ public class XORUnscrambleStream extends FilterInputStream {
return len; return len;
} }
/**
* What was used to unscramble the stream, it was already unscrambled, Main Seed, Prev Seed
*
* @return The Type of Seed used to unscramble this, if any
*/
public SuccessType getSuccessType() {
return successType;
}
@SuppressWarnings("ResultOfMethodCallIgnored") @SuppressWarnings("ResultOfMethodCallIgnored")
@Override @Override
public long skip(long n) throws IOException { public long skip(long n) throws IOException {
@@ -163,4 +179,11 @@ public class XORUnscrambleStream extends FilterInputStream {
} }
markRunning = running; markRunning = running;
} }
public static enum SuccessType {
UNSCRAMBLED,
MAIN_SEED,
PREV_SEED,
NONE;
}
} }
+48 -5
View File
@@ -1,14 +1,11 @@
package com.dre.brewery.utility; package com.dre.brewery.utility;
import com.dre.brewery.P; import com.dre.brewery.P;
import org.bukkit.Bukkit; import org.bukkit.*;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.TreeSpecies;
import org.bukkit.World;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData; import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Levelled; import org.bukkit.block.data.Levelled;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.material.Cauldron; import org.bukkit.material.Cauldron;
import org.bukkit.material.MaterialData; import org.bukkit.material.MaterialData;
import org.bukkit.material.Tree; import org.bukkit.material.Tree;
@@ -30,6 +27,8 @@ public class LegacyUtil {
private static Method GET_BLOCK_TYPE_ID_AT; private static Method GET_BLOCK_TYPE_ID_AT;
private static Method SET_DATA; private static Method SET_DATA;
public static boolean NewNbtVer;
static { static {
// <= 1.12.2 methods // <= 1.12.2 methods
// These will be rarely used // These will be rarely used
@@ -248,4 +247,48 @@ public class LegacyUtil {
} }
} }
/**
* MC 1.13 uses a different NBT API than the newer versions..
* We decide here which to use, the new or the old
*
* @return true if we can use nbt at all
*/
public static boolean initNbt() {
try {
Class.forName("org.bukkit.persistence.PersistentDataContainer");
NewNbtVer = true;
P.p.debugLog("Using the NEW nbt api");
return true;
} catch (ClassNotFoundException e) {
try {
Class.forName("org.bukkit.inventory.meta.tags.CustomItemTagContainer");
NewNbtVer = false;
P.p.debugLog("Using the OLD nbt api");
return true;
} catch (ClassNotFoundException ex) {
NewNbtVer = false;
P.p.debugLog("No nbt api found, using Lore Save System");
return false;
}
}
}
@SuppressWarnings("deprecation")
public static void writeBytesItem(byte[] bytes, ItemMeta meta, NamespacedKey key) {
if (NewNbtVer) {
meta.getPersistentDataContainer().set(key, org.bukkit.persistence.PersistentDataType.BYTE_ARRAY, bytes);
} else {
meta.getCustomTagContainer().setCustomTag(key, org.bukkit.inventory.meta.tags.ItemTagType.BYTE_ARRAY, bytes);
}
}
@SuppressWarnings("deprecation")
public static byte[] readBytesItem(ItemMeta meta, NamespacedKey key) {
if (NewNbtVer) {
return meta.getPersistentDataContainer().get(key, org.bukkit.persistence.PersistentDataType.BYTE_ARRAY);
} else {
return meta.getCustomTagContainer().getCustomTag(key, org.bukkit.inventory.meta.tags.ItemTagType.BYTE_ARRAY);
}
}
} }