Files
Rekkit/src/main/java/net/minecraft/server/ItemStack.java
Mike Primm 32924f9757 [Bleeding] Fix corruption due to thread safety issues. Fixes BUKKIT-3333
The 'tag' NBTTagCompound field of the ItemStack assumes that it is OK to
save a reference to an NBT supplied via load() and assumes it is OK to
supply a reference to the internal field during a save(). Neither is true,
as Chunk NBT structures are required to be read-only once created (due to
being written asynchronously off the server thread AND due to the potential
to be passed to a new Chunk if the same chunk is reloaded before the
writing of the NBT is completed by the File I/O thread). Keeping a live
reference to the NBT copy passed in, or to the NBT value passed back
during saving, creates serious thread safety issues which can result in
corrupted data being written to the world data files.

The specific issue here was uncovered by the recent change to use
setName("") on the ItemStack.tag object. When a chunk is being loaded
again before its save is completed, this results in name of the field
in the NBT being set to "". This causes it to be saved as "" instead
of "tag" resulting in it not being properly reloaded in the future which
results in the itemstack losing all of its metadata.
2012-12-30 23:31:22 -06:00

392 lines
11 KiB
Java

package net.minecraft.server;
public final class ItemStack {
public int count;
public int b;
public int id;
public NBTTagCompound tag;
private int damage;
private EntityItemFrame f;
public ItemStack(Block block) {
this(block, 1);
}
public ItemStack(Block block, int i) {
this(block.id, i, 0);
}
public ItemStack(Block block, int i, int j) {
this(block.id, i, j);
}
public ItemStack(Item item) {
this(item.id, 1, 0);
}
public ItemStack(Item item, int i) {
this(item.id, i, 0);
}
public ItemStack(Item item, int i, int j) {
this(item.id, i, j);
}
public ItemStack(int i, int j, int k) {
this.count = 0;
this.f = null;
this.id = i;
this.count = j;
this.setData(k); // CraftBukkit
}
public static ItemStack a(NBTTagCompound nbttagcompound) {
ItemStack itemstack = new ItemStack();
itemstack.c(nbttagcompound);
return itemstack.getItem() != null ? itemstack : null;
}
private ItemStack() {
this.count = 0;
this.f = null;
}
public ItemStack a(int i) {
ItemStack itemstack = new ItemStack(this.id, i, this.damage);
if (this.tag != null) {
itemstack.tag = (NBTTagCompound) this.tag.clone();
}
this.count -= i;
return itemstack;
}
public Item getItem() {
return Item.byId[this.id];
}
public boolean placeItem(EntityHuman entityhuman, World world, int i, int j, int k, int l, float f, float f1, float f2) {
boolean flag = this.getItem().interactWith(this, entityhuman, world, i, j, k, l, f, f1, f2);
if (flag) {
entityhuman.a(StatisticList.E[this.id], 1);
}
return flag;
}
public float a(Block block) {
return this.getItem().getDestroySpeed(this, block);
}
public ItemStack a(World world, EntityHuman entityhuman) {
return this.getItem().a(this, world, entityhuman);
}
public ItemStack b(World world, EntityHuman entityhuman) {
return this.getItem().b(this, world, entityhuman);
}
public NBTTagCompound save(NBTTagCompound nbttagcompound) {
nbttagcompound.setShort("id", (short) this.id);
nbttagcompound.setByte("Count", (byte) this.count);
nbttagcompound.setShort("Damage", (short) this.damage);
if (this.tag != null) {
nbttagcompound.set("tag", this.tag.clone()); // CraftBukkit - make defensive copy, data is going to another thread
}
return nbttagcompound;
}
public void c(NBTTagCompound nbttagcompound) {
this.id = nbttagcompound.getShort("id");
this.count = nbttagcompound.getByte("Count");
this.damage = nbttagcompound.getShort("Damage");
if (nbttagcompound.hasKey("tag")) {
// CraftBukkit - clear name from compound and make defensive copy as this data may be coming from the save thread
this.tag = (NBTTagCompound) nbttagcompound.getCompound("tag").clone().setName("");
}
}
public int getMaxStackSize() {
return this.getItem().getMaxStackSize();
}
public boolean isStackable() {
return this.getMaxStackSize() > 1 && (!this.f() || !this.h());
}
public boolean f() {
return Item.byId[this.id].getMaxDurability() > 0;
}
public boolean usesData() {
return Item.byId[this.id].l();
}
public boolean h() {
return this.f() && this.damage > 0;
}
public int i() {
return this.damage;
}
public int getData() {
return this.damage;
}
public void setData(int i) {
this.damage = (this.id > 0) && (this.id < 256) && (this.id != Block.ANVIL.id) ? Item.byId[this.id].filterData(i) : i; // CraftBukkit
}
public int k() {
return Item.byId[this.id].getMaxDurability();
}
public void damage(int i, EntityLiving entityliving) {
if (this.f()) {
if (i > 0 && entityliving instanceof EntityHuman) {
int j = EnchantmentManager.getEnchantmentLevel(Enchantment.DURABILITY.id, this);
int k = 0;
for (int l = 0; j > 0 && l < i; ++l) {
if (EnchantmentDurability.a(this, j, entityliving.world.random)) {
++k;
}
}
i -= k;
if (i <= 0) {
return;
}
}
if (!(entityliving instanceof EntityHuman) || !((EntityHuman) entityliving).abilities.canInstantlyBuild) {
this.damage += i;
}
if (this.damage > this.k()) {
entityliving.a(this);
if (entityliving instanceof EntityHuman) {
((EntityHuman) entityliving).a(StatisticList.F[this.id], 1);
}
--this.count;
if (this.count < 0) {
this.count = 0;
}
// CraftBukkit start - Check for item breaking
if (this.count == 0 && entityliving instanceof EntityHuman) {
org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemBreakEvent((EntityHuman) entityliving, this);
}
// CraftBukkit end
this.damage = 0;
}
}
}
public void a(EntityLiving entityliving, EntityHuman entityhuman) {
boolean flag = Item.byId[this.id].a(this, entityliving, (EntityLiving) entityhuman);
if (flag) {
entityhuman.a(StatisticList.E[this.id], 1);
}
}
public void a(World world, int i, int j, int k, int l, EntityHuman entityhuman) {
boolean flag = Item.byId[this.id].a(this, world, i, j, k, l, entityhuman);
if (flag) {
entityhuman.a(StatisticList.E[this.id], 1);
}
}
public int a(Entity entity) {
return Item.byId[this.id].a(entity);
}
public boolean b(Block block) {
return Item.byId[this.id].canDestroySpecialBlock(block);
}
public boolean a(EntityLiving entityliving) {
return Item.byId[this.id].a(this, entityliving);
}
public ItemStack cloneItemStack() {
ItemStack itemstack = new ItemStack(this.id, this.count, this.damage);
if (this.tag != null) {
itemstack.tag = (NBTTagCompound) this.tag.clone();
}
return itemstack;
}
public static boolean equals(ItemStack itemstack, ItemStack itemstack1) {
return itemstack == null && itemstack1 == null ? true : (itemstack != null && itemstack1 != null ? (itemstack.tag == null && itemstack1.tag != null ? false : itemstack.tag == null || itemstack.tag.equals(itemstack1.tag)) : false);
}
public static boolean matches(ItemStack itemstack, ItemStack itemstack1) {
return itemstack == null && itemstack1 == null ? true : (itemstack != null && itemstack1 != null ? itemstack.d(itemstack1) : false);
}
private boolean d(ItemStack itemstack) {
return this.count != itemstack.count ? false : (this.id != itemstack.id ? false : (this.damage != itemstack.damage ? false : (this.tag == null && itemstack.tag != null ? false : this.tag == null || this.tag.equals(itemstack.tag))));
}
public boolean doMaterialsMatch(ItemStack itemstack) {
return this.id == itemstack.id && this.damage == itemstack.damage;
}
public String a() {
return Item.byId[this.id].d(this);
}
public static ItemStack b(ItemStack itemstack) {
return itemstack == null ? null : itemstack.cloneItemStack();
}
public String toString() {
return this.count + "x" + Item.byId[this.id].getName() + "@" + this.damage;
}
public void a(World world, Entity entity, int i, boolean flag) {
if (this.b > 0) {
--this.b;
}
Item.byId[this.id].a(this, world, entity, i, flag);
}
public void a(World world, EntityHuman entityhuman, int i) {
entityhuman.a(StatisticList.D[this.id], i);
Item.byId[this.id].d(this, world, entityhuman);
}
public int m() {
return this.getItem().c_(this);
}
public EnumAnimation n() {
return this.getItem().b_(this);
}
public void b(World world, EntityHuman entityhuman, int i) {
this.getItem().a(this, world, entityhuman, i);
}
public boolean hasTag() {
return this.tag != null;
}
public NBTTagCompound getTag() {
return this.tag;
}
public NBTTagList getEnchantments() {
return this.tag == null ? null : (NBTTagList) this.tag.get("ench");
}
public void setTag(NBTTagCompound nbttagcompound) {
this.tag = nbttagcompound;
}
public String r() {
String s = this.getItem().l(this);
if (this.tag != null && this.tag.hasKey("display")) {
NBTTagCompound nbttagcompound = this.tag.getCompound("display");
if (nbttagcompound.hasKey("Name")) {
s = nbttagcompound.getString("Name");
}
}
return s;
}
public void c(String s) {
if (this.tag == null) {
this.tag = new NBTTagCompound();
}
if (!this.tag.hasKey("display")) {
this.tag.setCompound("display", new NBTTagCompound());
}
this.tag.getCompound("display").setString("Name", s);
}
public boolean s() {
return this.tag == null ? false : (!this.tag.hasKey("display") ? false : this.tag.getCompound("display").hasKey("Name"));
}
public boolean v() {
return !this.getItem().d_(this) ? false : !this.hasEnchantments();
}
public void addEnchantment(Enchantment enchantment, int i) {
if (this.tag == null) {
this.setTag(new NBTTagCompound());
}
if (!this.tag.hasKey("ench")) {
this.tag.set("ench", new NBTTagList("ench"));
}
NBTTagList nbttaglist = (NBTTagList) this.tag.get("ench");
NBTTagCompound nbttagcompound = new NBTTagCompound();
nbttagcompound.setShort("id", (short) enchantment.id);
nbttagcompound.setShort("lvl", (short) ((byte) i));
nbttaglist.add(nbttagcompound);
}
public boolean hasEnchantments() {
return this.tag != null && this.tag.hasKey("ench");
}
public void a(String s, NBTBase nbtbase) {
if (this.tag == null) {
this.setTag(new NBTTagCompound());
}
this.tag.set(s, nbtbase);
}
public boolean x() {
return this.getItem().x();
}
public boolean y() {
return this.f != null;
}
public void a(EntityItemFrame entityitemframe) {
this.f = entityitemframe;
}
public EntityItemFrame z() {
return this.f;
}
public int getRepairCost() {
return this.hasTag() && this.tag.hasKey("RepairCost") ? this.tag.getInt("RepairCost") : 0;
}
public void setRepairCost(int i) {
if (!this.hasTag()) {
this.tag = new NBTTagCompound();
}
this.tag.setInt("RepairCost", i);
}
}