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

View File

@ -76,7 +76,7 @@
</repository>
<repository>
<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>
<id>vault-repo</id>
@ -111,7 +111,7 @@
<dependency>
<groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId>
<version>1.14-R0.1-SNAPSHOT</version>
<version>1.14.4-R0.1-SNAPSHOT</version>
<scope>provided</scope>
<exclusions>
<exclusion>

View File

@ -60,7 +60,9 @@ public class BDistiller {
final int fuel = standInv.getHolder().getFuelLevel();
// 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

View File

@ -16,10 +16,7 @@ import org.bukkit.potion.PotionEffectType;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nullable;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.*;
import java.security.InvalidKeyException;
import java.security.SecureRandom;
import java.util.ArrayList;
@ -44,6 +41,7 @@ public class Brew {
private boolean persistent; // Only for legacy
private boolean immutable; // static/immutable potions should not be changed
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) {
this.ingredients = ingredients;
@ -78,7 +76,7 @@ public class Brew {
// returns a Brew by ItemMeta
@Nullable
public static Brew get(ItemMeta meta) {
if (!meta.hasLore()) return null;
if (!P.useNBT && !meta.hasLore()) return null;
Brew brew = load(meta);
@ -96,7 +94,7 @@ public class Brew {
if (!item.hasItemMeta()) return null;
ItemMeta meta = item.getItemMeta();
if (!meta.hasLore()) return null;
if (!P.useNBT && !meta.hasLore()) return null;
Brew brew = load(meta);
@ -104,7 +102,16 @@ public class Brew {
// Load Legacy and convert
brew = getFromPotionEffect(((PotionMeta) meta), true);
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);
item.setItemMeta(meta);
}
@ -434,6 +441,14 @@ public class Brew {
return unlabeled;
}
public boolean needsSave() {
return needsSave;
}
public void setNeedsSave(boolean needsSave) {
this.needsSave = needsSave;
}
// Set the Static flag, so potion is unchangeable
public void setStatic(boolean immutable, ItemStack potion) {
this.immutable = immutable;
@ -660,14 +675,25 @@ public class Brew {
}
private static Brew load(ItemMeta meta) {
LoreLoadStream loreStream;
try {
loreStream = new LoreLoadStream(meta, 0);
} catch (IllegalArgumentException ignored) {
// No Brew data found in Meta
return null;
InputStream itemLoadStream = null;
if (P.useNBT) {
// Try loading the Item Data from PersistentDataContainer
NBTLoadStream nbtStream = new NBTLoadStream(meta);
if (nbtStream.hasData()) {
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)) {
boolean parityFailed = false;
if (in.readByte() != 86) {
@ -678,8 +704,10 @@ public class Brew {
byte ver = in.readByte();
switch (ver) {
case 1:
unscrambler.start();
brew.loadFromStream(in);
break;
default:
if (parityFailed) {
@ -689,6 +717,18 @@ public class Brew {
}
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;
} catch (IOException e) {
P.p.errorLog("IO Error while loading Brew");
@ -723,9 +763,15 @@ public class Brew {
ingredients = BIngredients.load(in);
}
// Save brew data into meta/lore
// Save brew data into meta: lore/nbt
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)) {
out.writeByte(86); // Parity/sanity
out.writeByte(1); // Version

View File

@ -9,6 +9,7 @@ import com.dre.brewery.integration.IntegrationListener;
import com.dre.brewery.integration.LogBlockBarrel;
import com.dre.brewery.listeners.*;
import com.dre.brewery.utility.BUtil;
import com.dre.brewery.utility.LegacyUtil;
import org.apache.commons.lang.math.NumberUtils;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
@ -22,6 +23,7 @@ public class P extends JavaPlugin {
public static P p;
public static boolean debug;
public static boolean useUUID;
public static boolean useNBT;
public static boolean use1_9;
public static boolean use1_11;
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_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");
/*long master = new SecureRandom().nextLong();

View File

@ -189,12 +189,24 @@ public class BrewLore {
}
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("")) {
lore.remove(0);
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
public static boolean hasColorLore(PotionMeta meta) {
if (!meta.hasLore()) return false;

View File

@ -8,6 +8,8 @@ import java.util.List;
public class LoreLoadStream extends ByteArrayInputStream {
public static final String IDENTIFIER = "§%";
public LoreLoadStream(ItemMeta meta) throws IllegalArgumentException {
this(meta, -1);
}
@ -21,12 +23,12 @@ public class LoreLoadStream extends ByteArrayInputStream {
List<String> lore = meta.getLore();
if (lineNum >= 0) {
String line = lore.get(lineNum);
if (line.startsWith("§%")) {
if (line.startsWith(IDENTIFIER)) {
return loreLineToBytes(line);
}
}
for (String line : lore) {
if (line.startsWith("§%")) {
if (line.startsWith(IDENTIFIER)) {
return loreLineToBytes(line);
}
}

View File

@ -10,6 +10,8 @@ import java.util.List;
public class LoreSaveStream extends ByteArrayOutputStream {
public static final String IDENTIFIER = "§%";
private ItemMeta meta;
private int line;
private boolean flushed = false;
@ -38,7 +40,7 @@ public class LoreSaveStream extends ByteArrayOutputStream {
String s = toString();
StringBuilder loreLineBuilder = new StringBuilder((s.length() * 2) + 6);
loreLineBuilder.append("§%");
loreLineBuilder.append(IDENTIFIER);
for (char c : s.toCharArray()) {
loreLineBuilder.append('§').append(c);
}
@ -50,7 +52,7 @@ public class LoreSaveStream extends ByteArrayOutputStream {
}
int prev = 0;
for (Iterator<String> iterator = lore.iterator(); iterator.hasNext(); ) {
if (iterator.next().startsWith("§%")) {
if (iterator.next().startsWith(IDENTIFIER)) {
iterator.remove();
break;
}

View File

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

View File

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

View File

@ -23,6 +23,8 @@ public class XORUnscrambleStream extends FilterInputStream {
private boolean markRunning;
private boolean markxor;
private SuccessType successType = SuccessType.NONE;
/**
* 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());
if (id == 0) {
running = false;
successType = SuccessType.UNSCRAMBLED;
P.p.debugLog("Unscrambled data");
return;
}
int parity = in.read();
xorStream = new SeedInputStream(seed ^ id);
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) {
for (int i = prevSeeds.size() - 1; i >= 0; i--) {
seed = prevSeeds.get(i);
xorStream = new SeedInputStream(seed ^ id);
if (success = checkParity(parity)) {
successType = SuccessType.PREV_SEED;
P.p.debugLog("Had to use prevSeed to unscramble");
break;
}
@ -118,6 +125,15 @@ public class XORUnscrambleStream extends FilterInputStream {
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")
@Override
public long skip(long n) throws IOException {
@ -163,4 +179,11 @@ public class XORUnscrambleStream extends FilterInputStream {
}
markRunning = running;
}
public static enum SuccessType {
UNSCRAMBLED,
MAIN_SEED,
PREV_SEED,
NONE;
}
}

View File

@ -1,14 +1,11 @@
package com.dre.brewery.utility;
import com.dre.brewery.P;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.TreeSpecies;
import org.bukkit.World;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Levelled;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.material.Cauldron;
import org.bukkit.material.MaterialData;
import org.bukkit.material.Tree;
@ -30,6 +27,8 @@ public class LegacyUtil {
private static Method GET_BLOCK_TYPE_ID_AT;
private static Method SET_DATA;
public static boolean NewNbtVer;
static {
// <= 1.12.2 methods
// 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);
}
}
}