Experimental PlayerInventory serdes support.

This commit is contained in:
Olof Larsson 2013-03-23 16:13:56 +01:00
parent 4c70897d87
commit 9cc687dab0
3 changed files with 312 additions and 24 deletions

View File

@ -2,11 +2,14 @@ package com.massivecraft.mcore.adapter;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import org.bukkit.Bukkit; import org.bukkit.craftbukkit.v1_5_R2.inventory.CraftInventoryCustom;
import org.bukkit.craftbukkit.v1_5_R2.inventory.CraftInventoryPlayer;
import org.bukkit.inventory.Inventory; import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import com.massivecraft.mcore.MCore; import com.massivecraft.mcore.MCore;
import com.massivecraft.mcore.inventory.MCorePlayerInventory;
import com.massivecraft.mcore.xlib.gson.JsonDeserializationContext; import com.massivecraft.mcore.xlib.gson.JsonDeserializationContext;
import com.massivecraft.mcore.xlib.gson.JsonDeserializer; import com.massivecraft.mcore.xlib.gson.JsonDeserializer;
import com.massivecraft.mcore.xlib.gson.JsonElement; import com.massivecraft.mcore.xlib.gson.JsonElement;
@ -16,6 +19,11 @@ import com.massivecraft.mcore.xlib.gson.JsonPrimitive;
import com.massivecraft.mcore.xlib.gson.JsonSerializationContext; import com.massivecraft.mcore.xlib.gson.JsonSerializationContext;
import com.massivecraft.mcore.xlib.gson.JsonSerializer; import com.massivecraft.mcore.xlib.gson.JsonSerializer;
/**
* This is my Gson adapter for Inventories.
* It handles all inventories as CraftInventoryCustom "Chest"s with size of your choice
* except for PlayerInventory which it handles pretty darn well!
*/
public class InventoryAdapter implements JsonDeserializer<Inventory>, JsonSerializer<Inventory> public class InventoryAdapter implements JsonDeserializer<Inventory>, JsonSerializer<Inventory>
{ {
// -------------------------------------------- // // -------------------------------------------- //
@ -24,6 +32,13 @@ public class InventoryAdapter implements JsonDeserializer<Inventory>, JsonSerial
public static final String SIZE = "size"; public static final String SIZE = "size";
public static final String PLAYER = "player";
public static final String HELMET = "helmet";
public static final String CHESTPLATE = "chestplate";
public static final String LEGGINGS = "leggings";
public static final String BOOTS = "boots";
// -------------------------------------------- // // -------------------------------------------- //
// IMPLEMENTATION // IMPLEMENTATION
// -------------------------------------------- // // -------------------------------------------- //
@ -46,42 +61,161 @@ public class InventoryAdapter implements JsonDeserializer<Inventory>, JsonSerial
public static JsonElement toJson(Inventory src) public static JsonElement toJson(Inventory src)
{ {
// The return value is this object:
JsonObject jsonInventory = new JsonObject(); JsonObject jsonInventory = new JsonObject();
ItemStack[] itemStacks = src.getContents();
jsonInventory.add(SIZE, new JsonPrimitive(itemStacks.length));
// These variables are used in loops and repetitive logic.
ItemStack itemStack = null;
JsonElement jsonItemStack = null;
// Every inventory has a content part. Lets handle it at once:
ItemStack[] itemStacks = src.getContents();
for (int i = 0; i < itemStacks.length; i++) for (int i = 0; i < itemStacks.length; i++)
{ {
ItemStack itemStack = itemStacks[i]; itemStack = itemStacks[i];
JsonElement jsonItemStack = MCore.gson.toJsonTree(itemStack, ItemStack.class); jsonItemStack = MCore.gson.toJsonTree(itemStack, ItemStack.class);
if (jsonItemStack == null) continue; if (jsonItemStack == null) continue;
jsonInventory.add(String.valueOf(i), jsonItemStack); jsonInventory.add(String.valueOf(i), jsonItemStack);
} }
if (src instanceof PlayerInventory)
{
// Add the size "player"
jsonInventory.addProperty(SIZE, PLAYER);
// Cast to PlayerInventory
PlayerInventory psrc = (PlayerInventory)src;
// helmet
itemStack = psrc.getHelmet();
if (itemStack != null)
{
jsonItemStack = MCore.gson.toJsonTree(itemStack, ItemStack.class);
jsonInventory.add(HELMET, jsonItemStack);
}
// chestplate
itemStack = psrc.getChestplate();
if (itemStack != null)
{
jsonItemStack = MCore.gson.toJsonTree(itemStack, ItemStack.class);
jsonInventory.add(CHESTPLATE, jsonItemStack);
}
// leggings
itemStack = psrc.getLeggings();
if (itemStack != null)
{
jsonItemStack = MCore.gson.toJsonTree(itemStack, ItemStack.class);
jsonInventory.add(LEGGINGS, jsonItemStack);
}
// boots
itemStack = psrc.getBoots();
if (itemStack != null)
{
jsonItemStack = MCore.gson.toJsonTree(itemStack, ItemStack.class);
jsonInventory.add(BOOTS, jsonItemStack);
}
}
else
{
// Add the size *length*
jsonInventory.addProperty(SIZE, itemStacks.length);
}
return jsonInventory; return jsonInventory;
} }
public static Inventory fromJson(JsonElement json) public static Inventory fromJson(JsonElement json)
{ {
// If must be an object!
if ( ! json.isJsonObject()) return null; if ( ! json.isJsonObject()) return null;
JsonObject jsonInventory = json.getAsJsonObject(); JsonObject jsonInventory = json.getAsJsonObject();
// The return value
Inventory ret = null;
// These variables are used in loops and repetitive logic.
ItemStack itemStack = null;
JsonElement jsonItemStack = null;
// There must be a size entry!
if ( ! jsonInventory.has(SIZE)) return null; if ( ! jsonInventory.has(SIZE)) return null;
int size = jsonInventory.get(SIZE).getAsInt(); JsonPrimitive jsonSize = jsonInventory.get(SIZE).getAsJsonPrimitive();
int size = 0;
// What size/type is it?
if (jsonSize.isString())
{
// Only makes sense if stating "player".
if (!jsonSize.getAsString().equals(PLAYER)) return null;
// We use 36 here since it's the size of the player inventory (without armor)
size = 36;
// This is a PlayerInventory
ret = new CraftInventoryPlayer(new MCorePlayerInventory());
PlayerInventory pret = (PlayerInventory)ret;
// helmet
if (jsonInventory.has(HELMET))
{
jsonItemStack = jsonInventory.get(HELMET);
itemStack = MCore.gson.fromJson(jsonItemStack, ItemStack.class);
pret.setHelmet(itemStack);
}
// chestplate
if (jsonInventory.has(CHESTPLATE))
{
jsonItemStack = jsonInventory.get(CHESTPLATE);
itemStack = MCore.gson.fromJson(jsonItemStack, ItemStack.class);
pret.setChestplate(itemStack);
}
// leggings
if (jsonInventory.has(LEGGINGS))
{
jsonItemStack = jsonInventory.get(LEGGINGS);
itemStack = MCore.gson.fromJson(jsonItemStack, ItemStack.class);
pret.setLeggings(itemStack);
}
// boots
if (jsonInventory.has(BOOTS))
{
jsonItemStack = jsonInventory.get(BOOTS);
itemStack = MCore.gson.fromJson(jsonItemStack, ItemStack.class);
pret.setBoots(itemStack);
}
}
else if (jsonSize.isNumber())
{
// A custom size were specified
size = jsonSize.getAsInt();
// This is a "Custom" Inventory (content only).
ret = new CraftInventoryCustom(null, size);
}
else
{
// It must be either string or number
return null;
}
// Now process content
ItemStack[] itemStacks = new ItemStack[size]; ItemStack[] itemStacks = new ItemStack[size];
for (int i = 0; i < size; i++) for (int i = 0; i < size; i++)
{ {
// Fetch the jsonItemStack or mark it as empty and continue // Fetch the jsonItemStack or mark it as empty and continue
String stackIdx = String.valueOf(i); String stackIdx = String.valueOf(i);
JsonElement jsonItemStack = jsonInventory.get(stackIdx); jsonItemStack = jsonInventory.get(stackIdx);
ItemStack itemStack = MCore.gson.fromJson(jsonItemStack, ItemStack.class); itemStack = MCore.gson.fromJson(jsonItemStack, ItemStack.class);
itemStacks[i] = itemStack; itemStacks[i] = itemStack;
} }
Inventory ret = Bukkit.createInventory(null, size);
ret.setContents(itemStacks); ret.setContents(itemStacks);
return ret; return ret;
} }

View File

@ -0,0 +1,111 @@
package com.massivecraft.mcore.inventory;
import org.bukkit.inventory.InventoryHolder;
import net.minecraft.server.v1_5_R2.EntityHuman;
import net.minecraft.server.v1_5_R2.ItemStack;
import net.minecraft.server.v1_5_R2.PlayerInventory;
/**
* This is an extended version of the NMS.PlayerInventory.
* It is extended in such a way that it has a no-arg constructor.
* It is mainly used for deserialization of PlayerInventor.
*
* What is tricky about the NMS.PlayerInventory is that it does hold a link/field to the holder/player.
* It is however acceptable for this field "public EntityHuman player" to be null as long as some internal methods are rewritten.
*
* NPE evasion is achieved by overriding all internal methods using the player field.
*
* How to update:
* Do go to NMS.PlayerInventory and search for references to "player".
* As of 1.5.1 these are the references:
*
* a(EntityHuman) (2 matches)
* g(int)
* getOwner()
* k() (2 matches)
* m() (2 matches)
* pickup(ItemStack) (2 matches)
* PlayerInventory(EntityHuman)
*
*/
public class MCorePlayerInventory extends PlayerInventory
{
// -------------------------------------------- //
// CONSTRUCT
// -------------------------------------------- //
public MCorePlayerInventory()
{
super(null);
}
// -------------------------------------------- //
// NPE EVASION
// -------------------------------------------- //
// Is the entityhuman within reach?
// Can it edit the inventory content?
// According to the source code design entityhuman is never null. However we go for safety first.
@Override
public boolean a(EntityHuman entityhuman)
{
// Null cases
if (entityhuman == null) return true;
if (this.player == null) return true;
// Other people can reach at any time
if (!this.player.equals(entityhuman)) return true;
return super.a(entityhuman);
}
// This method handles damage dealt to the armor inside the inventory.
// We simply ignore damage if there is no player.
@Override
public void g(int arg0)
{
if (this.player == null) return;
super.g(arg0);
}
// If the player is null there is no owner.
@Override
public InventoryHolder getOwner()
{
if (this.player == null) return null;
return super.getOwner();
}
// This method looks like some sort of recurring tick to the items but not inventories.
// Let's just ignore it if the player is elsewhere.
@Override
public void k()
{
if (this.player == null) return;
super.k();
}
// Called when the player dies and no items should be kept.
// This will cause the player to drop all the items.
@Override
public void m()
{
if (this.player == null) return;
super.m();
}
// Pickup the item. Return if the pickup was successful or not.
@Override
public boolean pickup(ItemStack arg0)
{
if (this.player == null) return false;
return super.pickup(arg0);
}
}

View File

@ -2,14 +2,18 @@ package com.massivecraft.mcore.util;
import java.util.HashMap; import java.util.HashMap;
import org.bukkit.Bukkit;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.craftbukkit.v1_5_R2.inventory.CraftInventoryCustom;
import org.bukkit.craftbukkit.v1_5_R2.inventory.CraftInventoryPlayer;
import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryType; import org.bukkit.event.inventory.InventoryType;
import org.bukkit.event.inventory.InventoryType.SlotType; import org.bukkit.event.inventory.InventoryType.SlotType;
import org.bukkit.inventory.Inventory; import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import com.massivecraft.mcore.inventory.MCorePlayerInventory;
public class InventoryUtil public class InventoryUtil
{ {
@ -115,16 +119,38 @@ public class InventoryUtil
public static boolean isEmpty(Inventory inv) public static boolean isEmpty(Inventory inv)
{ {
if (inv == null) return true; if (inv == null) return true;
for (ItemStack stack : inv.getContents())
for (ItemStack itemStack : inv.getContents())
{ {
if (stack == null) continue; if (isSomething(itemStack)) return false;
if (stack.getAmount() == 0) continue;
if (stack.getTypeId() == 0) continue;
return false;
} }
if (inv instanceof PlayerInventory)
{
PlayerInventory pinv = (PlayerInventory)inv;
if (isSomething(pinv.getHelmet())) return false;
if (isSomething(pinv.getChestplate())) return false;
if (isSomething(pinv.getLeggings())) return false;
if (isSomething(pinv.getBoots())) return false;
}
return true; return true;
} }
public static boolean isNothing(ItemStack itemStack)
{
if (itemStack == null) return true;
if (itemStack.getAmount() == 0) return true;
if (itemStack.getTypeId() == 0) return true;
return false;
}
public static boolean isSomething(ItemStack itemStack)
{
return !isNothing(itemStack);
}
// -------------------------------------------- // // -------------------------------------------- //
// CLONE ITEMSTACKS/INVENTORY // CLONE ITEMSTACKS/INVENTORY
// -------------------------------------------- // // -------------------------------------------- //
@ -141,18 +167,35 @@ public class InventoryUtil
return ret; return ret;
} }
// NOTE: This method does not handle the armor part of player inventories.
// That is expected behavior for now.
public static Inventory cloneInventory(Inventory inventory) public static Inventory cloneInventory(Inventory inventory)
{ {
if (inventory == null) return null; if (inventory == null) return null;
InventoryHolder holder = inventory.getHolder(); Inventory ret = null;
int size = inventory.getSize();
String title = inventory.getTitle();
ItemStack[] contents = cloneItemStacks(inventory.getContents());
Inventory ret = Bukkit.createInventory(holder, size, title); int size = inventory.getSize();
InventoryHolder holder = inventory.getHolder();
String title = inventory.getTitle();
if (inventory instanceof PlayerInventory)
{
MCorePlayerInventory nmsret = new MCorePlayerInventory();
CraftInventoryPlayer pret = new CraftInventoryPlayer(nmsret);
ret = pret;
PlayerInventory pinventory = (PlayerInventory)inventory;
pret.setHelmet(pinventory.getHelmet() == null ? null : new ItemStack(pinventory.getHelmet()));
pret.setChestplate(pinventory.getChestplate() == null ? null : new ItemStack(pinventory.getChestplate()));
pret.setLeggings(pinventory.getLeggings() == null ? null : new ItemStack(pinventory.getLeggings()));
pret.setBoots(pinventory.getBoots() == null ? null : new ItemStack(pinventory.getBoots()));
}
else
{
ret = new CraftInventoryCustom(holder, size, title);
}
ItemStack[] contents = cloneItemStacks(inventory.getContents());
ret.setContents(contents); ret.setContents(contents);
return ret; return ret;