Skip to content

Instantly share code, notes, and snippets.

@aadnk
Last active November 30, 2015 18:35
Show Gist options
  • Save aadnk/11343062 to your computer and use it in GitHub Desktop.
Save aadnk/11343062 to your computer and use it in GitHub Desktop.
ItemSerialization for Minecraft 1.7.9 that supports player inventories.
package com.comphenix.example;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import net.minecraft.server.v1_7_R3.NBTBase;
import net.minecraft.server.v1_7_R3.NBTCompressedStreamTools;
import net.minecraft.server.v1_7_R3.NBTReadLimiter;
import net.minecraft.server.v1_7_R3.NBTTagCompound;
import net.minecraft.server.v1_7_R3.NBTTagList;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.craftbukkit.v1_7_R3.inventory.CraftInventoryCustom;
import org.bukkit.craftbukkit.v1_7_R3.inventory.CraftItemStack;
import org.bukkit.entity.HumanEntity;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
// For 1.7.9
public class ItemSerialization {
// Current serialization version - we will use this to handle compatiblity
private static final int VERSION = 1;
// Types we can serialize
private static final String INVENTORY_CUSTOM = "custom";
private static final String INVENTORY_PLAYER = "player";
// NBT types
private static final int NBT_TYPE_COMPOUND = 10;
// NBT fields
private static final String NBT_TYPE = "type";
private static final String NBT_ITEMS = "items";
private static final String NBT_VERSION = "version";
private static final String NBT_PLAYER_HELD_INDEX = "player_held_index";
private static final String NBT_PLAYER_NAME = "player_name"; // TODO: Change to UUID
private static Method WRITE_NBT;
private static Method READ_NBT;
public static String toBase64(Inventory inventory) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
DataOutputStream dataOutput = new DataOutputStream(outputStream);
NBTTagCompound root = new NBTTagCompound();
NBTTagList itemList = new NBTTagList();
String type = INVENTORY_CUSTOM;
int size = inventory.getSize();
// Inventory types we support
if (inventory instanceof PlayerInventory) {
PlayerInventory playerInventory = (PlayerInventory) inventory;
root.setInt(NBT_PLAYER_HELD_INDEX, playerInventory.getHeldItemSlot());
root.setString(NBT_PLAYER_NAME, playerInventory.getHolder().getName());
type = INVENTORY_PLAYER;
size += 4;
}
// Save every element in the list
for (int i = 0; i < size; i++) {
NBTTagCompound outputObject = new NBTTagCompound();
CraftItemStack craft = getCraftVersion(inventory.getItem(i));
net.minecraft.server.v1_7_R3.ItemStack nmsCopy = CraftItemStack.asNMSCopy(craft);
// Convert the item stack to a NBT compound
if (nmsCopy != null) {
nmsCopy.save(outputObject);
}
itemList.add(outputObject);
}
// Now save the list
root.setString(NBT_TYPE, type);
root.setInt(NBT_VERSION, VERSION);
root.set(NBT_ITEMS, itemList);
writeNbt(root, dataOutput);
// Serialize that array
return Base64Coder.encodeLines(outputStream.toByteArray());
}
public static Inventory fromBase64(String data) {
ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64Coder.decodeLines(data));
NBTTagCompound root = (NBTTagCompound) readNbt(new DataInputStream(inputStream), 0); new NBTTagCompound();
// Future versions might handle older versions instead
if (root.getInt(NBT_VERSION) != VERSION) {
throw new IllegalArgumentException("Incompatible version: " + root.getInt(NBT_VERSION));
}
NBTTagList itemList = root.getList(NBT_ITEMS, NBT_TYPE_COMPOUND);
String type = root.getString(NBT_TYPE);
return parseInventory(root, itemList, type);
}
private static Inventory parseInventory(NBTTagCompound root, NBTTagList itemList, String type) {
Inventory inventory = new CraftInventoryCustom(null, itemList.size());
for (int i = 0; i < itemList.size(); i++) {
NBTTagCompound inputObject = (NBTTagCompound) itemList.get(i);
if (!inputObject.isEmpty()) {
inventory.setItem(i, CraftItemStack.asCraftMirror(
net.minecraft.server.v1_7_R3.ItemStack.createStack(inputObject)));
}
}
// Handle different types
if (INVENTORY_CUSTOM.equals(type)) {
return inventory;
} else if (INVENTORY_PLAYER.equals(type)) {
return new OfflinePlayerInventry(inventory,
root.getInt(NBT_PLAYER_HELD_INDEX),
root.getString(NBT_PLAYER_NAME));
} else {
throw new IllegalArgumentException("Unexpected inventory type: " + type);
}
}
private static void writeNbt(NBTBase base, DataOutput output) {
if (WRITE_NBT == null) {
try {
WRITE_NBT = NBTCompressedStreamTools.class.getDeclaredMethod("a", NBTBase.class, DataOutput.class);
WRITE_NBT.setAccessible(true);
} catch (Exception e) {
throw new IllegalStateException("Unable to find private write method.", e);
}
}
try {
WRITE_NBT.invoke(null, base, output);
} catch (Exception e) {
throw new IllegalArgumentException("Unable to write " + base + " to " + output, e);
}
}
private static NBTBase readNbt(DataInput input, int level) {
if (READ_NBT == null) {
try {
// NBTReadLimiter is new in 1.7.9
READ_NBT = NBTCompressedStreamTools.class.getDeclaredMethod("a", DataInput.class, int.class, NBTReadLimiter.class);
READ_NBT.setAccessible(true);
} catch (Exception e) {
throw new IllegalStateException("Unable to find private read method.", e);
}
}
try {
return (NBTBase) READ_NBT.invoke(null, input, level, NBTReadLimiter.a);
} catch (Exception e) {
throw new IllegalArgumentException("Unable to read from " + input, e);
}
}
public static Inventory getInventoryFromArray(ItemStack[] items) {
CraftInventoryCustom custom = new CraftInventoryCustom(null, items.length);
for (int i = 0; i < items.length; i++) {
if (items[i] != null) {
custom.setItem(i, items[i]);
}
}
return custom;
}
private static CraftItemStack getCraftVersion(ItemStack stack) {
if (stack instanceof CraftItemStack)
return (CraftItemStack) stack;
else if (stack != null)
return CraftItemStack.asCraftCopy(stack);
else
return null;
}
private static class OfflinePlayerInventry extends ForwardingInventory implements PlayerInventory {
private int heldItemSlot;
private String playerName;
public OfflinePlayerInventry(Inventory delegate, int heldItemSlot, String playerName) {
super(delegate, InventoryType.PLAYER);
this.heldItemSlot = heldItemSlot;
}
@SuppressWarnings("deprecation")
@Override
public HumanEntity getHolder() {
return Bukkit.getPlayer(playerName);
}
@Override
public int getSize() {
// Offset
return super.getSize() - 4;
}
@Override
public ItemStack[] getContents() {
return Arrays.copyOf(super.getContents(), getSize());
}
@Override
public ItemStack[] getArmorContents() {
return Iterables.toArray(Iterables.skip(this, getSize()), ItemStack.class);
}
@Override
public ItemStack getHelmet() {
return getItem(getSize() + 3);
}
@Override
public ItemStack getChestplate() {
return getItem(getSize() + 2);
}
@Override
public ItemStack getLeggings() {
return getItem(getSize() + 1);
}
@Override
public ItemStack getBoots() {
return getItem(getSize() + 0);
}
@Override
public void setArmorContents(ItemStack[] items) {
int cnt = getSize();
if (items == null) {
items = new org.bukkit.inventory.ItemStack[4];
}
for (org.bukkit.inventory.ItemStack item : items) {
setItem(cnt++, item);
}
}
@Override
public void setHelmet(ItemStack helmet) {
setItem(getSize() + 3, helmet);
}
@Override
public void setChestplate(ItemStack chestplate) {
setItem(getSize() + 2, chestplate);
}
@Override
public void setLeggings(ItemStack leggings) {
setItem(getSize() + 1, leggings);
}
@Override
public void setBoots(ItemStack boots) {
setItem(getSize() + 0, boots);
}
@Override
public ItemStack getItemInHand() {
return getItem(heldItemSlot);
}
@Override
public void setItemInHand(ItemStack stack) {
setItem(getHeldItemSlot(), stack);
}
@Override
public int getHeldItemSlot() {
return heldItemSlot;
}
@Override
public void setHeldItemSlot(int slot) {
Preconditions.checkArgument((slot >= 0) && (slot < 8), "Slot is not between 0 and 8 inclusive");
this.heldItemSlot = slot;
}
@Override
@Deprecated
public int clear(int paramInt1, int paramInt2) {
throw new UnsupportedOperationException();
}
}
private static class ForwardingInventory implements Inventory {
protected Inventory delegate;
protected InventoryType type;
/**
* Construct a new inventory that forwards all method calls to another inventory.
* @param delegate - the inventory delegate.
* @param type - the type.
*/
public ForwardingInventory(Inventory delegate, InventoryType type) {
this.delegate = delegate;
this.type = type;
}
@Override
public InventoryType getType() {
if (type != null)
return type;
return delegate.getType();
}
@Override
public int getSize() {
return delegate.getSize();
}
@Override
public int getMaxStackSize() {
return delegate.getMaxStackSize();
}
@Override
public void setMaxStackSize(int paramInt) {
delegate.setMaxStackSize(paramInt);
}
@Override
public String getName() {
return delegate.getName();
}
@Override
public ItemStack getItem(int paramInt) {
return delegate.getItem(paramInt);
}
@Override
public void setItem(int paramInt, ItemStack paramItemStack) {
delegate.setItem(paramInt, paramItemStack);
}
@Override
public HashMap<Integer, ItemStack> addItem(ItemStack... paramArrayOfItemStack)
throws IllegalArgumentException {
return delegate.addItem(paramArrayOfItemStack);
}
@Override
public HashMap<Integer, ItemStack> removeItem(ItemStack... paramArrayOfItemStack)
throws IllegalArgumentException {
return delegate.removeItem(paramArrayOfItemStack);
}
@Override
public ItemStack[] getContents() {
return delegate.getContents();
}
@Override
public void setContents(ItemStack[] paramArrayOfItemStack) throws IllegalArgumentException {
delegate.setContents(paramArrayOfItemStack);
}
@Override
@SuppressWarnings("deprecation")
public boolean contains(int paramInt) {
return delegate.contains(paramInt);
}
@Override
public boolean contains(Material paramMaterial) throws IllegalArgumentException {
return delegate.contains(paramMaterial);
}
@Override
public boolean contains(ItemStack paramItemStack) {
return delegate.contains(paramItemStack);
}
@Override
@SuppressWarnings("deprecation")
public boolean contains(int paramInt1, int paramInt2) {
return delegate.contains(paramInt1, paramInt2);
}
@Override
public boolean contains(Material paramMaterial, int paramInt)
throws IllegalArgumentException {
return delegate.contains(paramMaterial, paramInt);
}
@Override
public boolean contains(ItemStack paramItemStack, int paramInt) {
return delegate.contains(paramItemStack, paramInt);
}
@Override
public boolean containsAtLeast(ItemStack paramItemStack, int paramInt) {
return delegate.containsAtLeast(paramItemStack, paramInt);
}
@Override
@SuppressWarnings("deprecation")
public HashMap<Integer, ? extends ItemStack> all(int paramInt) {
return delegate.all(paramInt);
}
@Override
public HashMap<Integer, ? extends ItemStack> all(Material paramMaterial)
throws IllegalArgumentException {
return delegate.all(paramMaterial);
}
@Override
public HashMap<Integer, ? extends ItemStack> all(ItemStack paramItemStack) {
return delegate.all(paramItemStack);
}
@Override
@SuppressWarnings("deprecation")
public int first(int paramInt) {
return delegate.first(paramInt);
}
@Override
public int first(Material paramMaterial) throws IllegalArgumentException {
return delegate.first(paramMaterial);
}
@Override
public int first(ItemStack paramItemStack) {
return delegate.first(paramItemStack);
}
@Override
public int firstEmpty() {
return delegate.firstEmpty();
}
@Override
@SuppressWarnings("deprecation")
public void remove(int paramInt) {
delegate.remove(paramInt);
}
@Override
public void remove(Material paramMaterial) throws IllegalArgumentException {
delegate.remove(paramMaterial);
}
@Override
public void remove(ItemStack paramItemStack) {
delegate.remove(paramItemStack);
}
@Override
public void clear(int paramInt) {
delegate.clear(paramInt);
}
@Override
public void clear() {
delegate.clear();
}
@Override
public List<HumanEntity> getViewers() {
return delegate.getViewers();
}
@Override
public String getTitle() {
return delegate.getTitle();
}
@Override
public InventoryHolder getHolder() {
return delegate.getHolder();
}
@Override
public ListIterator<ItemStack> iterator() {
return delegate.iterator();
}
@Override
public ListIterator<ItemStack> iterator(int paramInt) {
return delegate.iterator(paramInt);
}
@Override
public String toString() {
return type.toString();
}
}
}
@therealspoljo
Copy link

@aadnk Could you possibly update this class?

@raton023
Copy link

belven use craftbukkit on the libs not bukkit

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment