| package li.cil.oc.common.container |
| |
| import li.cil.oc.common |
| import li.cil.oc.common.InventorySlots.InventorySlot |
| import li.cil.oc.common.Tier |
| import li.cil.oc.server.{PacketSender => ServerPacketSender} |
| import li.cil.oc.util.SideTracker |
| import net.minecraft.entity.player.EntityPlayer |
| import net.minecraft.entity.player.EntityPlayerMP |
| import net.minecraft.entity.player.InventoryPlayer |
| import net.minecraft.inventory.Container |
| import net.minecraft.inventory.ICrafting |
| import net.minecraft.inventory.IInventory |
| import net.minecraft.inventory.Slot |
| import net.minecraft.item.ItemStack |
| import net.minecraft.nbt.NBTBase |
| import net.minecraft.nbt.NBTTagCompound |
| |
| import scala.collection.convert.WrapAsScala._ |
| |
| abstract class Player(val playerInventory: InventoryPlayer, val otherInventory: IInventory) extends Container { |
| /** Number of player inventory slots to display horizontally. */ |
| protected val playerInventorySizeX = math.min(9, InventoryPlayer.getHotbarSize) |
| |
| /** Subtract four for armor slots. */ |
| protected val playerInventorySizeY = math.min(4, (playerInventory.getSizeInventory - 4) / playerInventorySizeX) |
| |
| /** Render size of slots (width and height). */ |
| protected val slotSize = 18 |
| |
| override def canInteractWith(player: EntityPlayer) = otherInventory.isUseableByPlayer(player) |
| |
| override def slotClick(slot: Int, mouseClick: Int, holdingShift: Int, player: EntityPlayer) = { |
| val result = super.slotClick(slot, mouseClick, holdingShift, player) |
| if (SideTracker.isServer) { |
| detectAndSendChanges() // We have to enforce this more than MC does itself |
| // because stacks can change their... "character" just by being inserted in |
| // certain containers - by being assigned an address. |
| } |
| if (result != null && result.stackSize > 0) result |
| else null |
| } |
| |
| override def transferStackInSlot(player: EntityPlayer, index: Int): ItemStack = { |
| val slot = Option(inventorySlots.get(index)).map(_.asInstanceOf[Slot]).orNull |
| if (slot != null && slot.getHasStack) { |
| tryTransferStackInSlot(slot, slot.inventory == otherInventory) |
| if (SideTracker.isServer) { |
| detectAndSendChanges() |
| } |
| } |
| null |
| } |
| |
| protected def tryTransferStackInSlot(from: Slot, intoPlayerInventory: Boolean) { |
| val fromStack = from.getStack |
| var somethingChanged = false |
| |
| val step = if (intoPlayerInventory) -1 else 1 |
| val (begin, end) = |
| if (intoPlayerInventory) (inventorySlots.size - 1, 0) |
| else (0, inventorySlots.size - 1) |
| |
| if (fromStack.getMaxStackSize > 1) for (i <- begin to end by step if i >= 0 && i < inventorySlots.size && from.getHasStack && from.getStack.stackSize > 0) { |
| val intoSlot = inventorySlots.get(i).asInstanceOf[Slot] |
| if (intoSlot.inventory != from.inventory && intoSlot.getHasStack) { |
| val intoStack = intoSlot.getStack |
| val itemsAreEqual = fromStack.isItemEqual(intoStack) && ItemStack.areItemStackTagsEqual(fromStack, intoStack) |
| val maxStackSize = math.min(fromStack.getMaxStackSize, intoSlot.getSlotStackLimit) |
| val slotHasCapacity = intoStack.stackSize < maxStackSize |
| if (itemsAreEqual && slotHasCapacity) { |
| val itemsMoved = math.min(maxStackSize - intoStack.stackSize, fromStack.stackSize) |
| if (itemsMoved > 0) { |
| intoStack.stackSize += from.decrStackSize(itemsMoved).stackSize |
| intoSlot.onSlotChanged() |
| somethingChanged = true |
| } |
| } |
| } |
| } |
| |
| for (i <- begin to end by step if i >= 0 && i < inventorySlots.size && from.getHasStack && from.getStack.stackSize > 0) { |
| val intoSlot = inventorySlots.get(i).asInstanceOf[Slot] |
| if (intoSlot.inventory != from.inventory && !intoSlot.getHasStack && intoSlot.isItemValid(fromStack)) { |
| val maxStackSize = math.min(fromStack.getMaxStackSize, intoSlot.getSlotStackLimit) |
| val itemsMoved = math.min(maxStackSize, fromStack.stackSize) |
| intoSlot.putStack(from.decrStackSize(itemsMoved)) |
| if (maxStackSize == 0) { |
| // Special case: we have an inventory with "phantom/ghost stacks", i.e. |
| // zero size stacks, usually used for configuring machinery. In that |
| // case we stop early if whatever we're shift clicking is already in a |
| // slot of the target inventory. This workaround can be problematic if |
| // an inventory has both real and phantom slots, but we don't have |
| // something like that, yet, so hey. |
| return |
| } |
| somethingChanged = true |
| } |
| } |
| |
| if (somethingChanged) { |
| from.onSlotChanged() |
| } |
| } |
| |
| def addSlotToContainer(x: Int, y: Int, slot: String = common.Slot.Any, tier: Int = common.Tier.Any) { |
| val index = inventorySlots.size |
| addSlotToContainer(new StaticComponentSlot(this, otherInventory, index, x, y, slot, tier)) |
| } |
| |
| def addSlotToContainer(x: Int, y: Int, info: Array[Array[InventorySlot]], containerTierGetter: () => Int) { |
| val index = inventorySlots.size |
| addSlotToContainer(new DynamicComponentSlot(this, otherInventory, index, x, y, slot => info(slot.containerTierGetter())(slot.getSlotIndex), containerTierGetter)) |
| } |
| |
| def addSlotToContainer(x: Int, y: Int, info: DynamicComponentSlot => InventorySlot) { |
| val index = inventorySlots.size |
| addSlotToContainer(new DynamicComponentSlot(this, otherInventory, index, x, y, info, () => Tier.One)) |
| } |
| |
| /** Render player inventory at the specified coordinates. */ |
| protected def addPlayerInventorySlots(left: Int, top: Int) = { |
| // Show the inventory proper. Start at plus one to skip hot bar. |
| for (slotY <- 1 until playerInventorySizeY) { |
| for (slotX <- 0 until playerInventorySizeX) { |
| val index = slotX + slotY * playerInventorySizeX |
| val x = left + slotX * slotSize |
| // Compensate for hot bar offset. |
| val y = top + (slotY - 1) * slotSize |
| addSlotToContainer(new Slot(playerInventory, index, x, y)) |
| } |
| } |
| |
| // Show the quick slot bar below the internal inventory. |
| val quickBarSpacing = 4 |
| for (index <- 0 until playerInventorySizeX) { |
| val x = left + index * slotSize |
| val y = top + slotSize * (playerInventorySizeY - 1) + quickBarSpacing |
| addSlotToContainer(new Slot(playerInventory, index, x, y)) |
| } |
| } |
| |
| protected def sendProgressBarUpdate(id: Int, value: Int) { |
| for (entry <- crafters) entry match { |
| case player: ICrafting => player.sendProgressBarUpdate(this, id, value) |
| case _ => |
| } |
| } |
| |
| override def detectAndSendChanges(): Unit = { |
| super.detectAndSendChanges() |
| if (SideTracker.isServer) { |
| val nbt = new NBTTagCompound() |
| detectCustomDataChanges(nbt) |
| for (entry <- crafters) entry match { |
| case player: EntityPlayerMP => ServerPacketSender.sendContainerUpdate(this, nbt, player) |
| case _ => |
| } |
| } |
| } |
| |
| // Used for custom value synchronization, because shorts simply don't cut it most of the time. |
| protected def detectCustomDataChanges(nbt: NBTTagCompound): Unit = { |
| val delta = synchronizedData.getDelta |
| if (delta != null && !delta.hasNoTags) { |
| nbt.setTag("delta", delta) |
| } |
| } |
| |
| def updateCustomData(nbt: NBTTagCompound): Unit = { |
| if (nbt.hasKey("delta")) { |
| val delta = nbt.getCompoundTag("delta") |
| delta.func_150296_c().foreach { |
| case key: String => synchronizedData.setTag(key, delta.getTag(key)) |
| } |
| } |
| } |
| |
| protected class SynchronizedData extends NBTTagCompound { |
| private var delta = new NBTTagCompound() |
| |
| def getDelta: NBTTagCompound = this.synchronized { |
| if (delta.hasNoTags) null |
| else { |
| val result = delta |
| delta = new NBTTagCompound() |
| result |
| } |
| } |
| |
| override def setTag(key: String, value: NBTBase): Unit = this.synchronized { |
| if (!value.equals(getTag(key))) delta.setTag(key, value) |
| super.setTag(key, value) |
| } |
| |
| override def setByte(key: String, value: Byte): Unit = this.synchronized { |
| if (value != getByte(key)) delta.setByte(key, value) |
| super.setByte(key, value) |
| } |
| |
| override def setShort(key: String, value: Short): Unit = this.synchronized { |
| if (value != getShort(key)) delta.setShort(key, value) |
| super.setShort(key, value) |
| } |
| |
| override def setInteger(key: String, value: Int): Unit = this.synchronized { |
| if (value != getInteger(key)) delta.setInteger(key, value) |
| super.setInteger(key, value) |
| } |
| |
| override def setLong(key: String, value: Long): Unit = this.synchronized { |
| if (value != getLong(key)) delta.setLong(key, value) |
| super.setLong(key, value) |
| } |
| |
| override def setFloat(key: String, value: Float): Unit = this.synchronized { |
| if (value != getFloat(key)) delta.setFloat(key, value) |
| super.setFloat(key, value) |
| } |
| |
| override def setDouble(key: String, value: Double): Unit = this.synchronized { |
| if (value != getDouble(key)) delta.setDouble(key, value) |
| super.setDouble(key, value) |
| } |
| |
| override def setString(key: String, value: String): Unit = this.synchronized { |
| if (value != getString(key)) delta.setString(key, value) |
| super.setString(key, value) |
| } |
| |
| override def setByteArray(key: String, value: Array[Byte]): Unit = this.synchronized { |
| if (value.deep != getByteArray(key).deep) delta.setByteArray(key, value) |
| super.setByteArray(key, value) |
| } |
| |
| override def setIntArray(key: String, value: Array[Int]): Unit = this.synchronized { |
| if (value.deep != getIntArray(key).deep) delta.setIntArray(key, value) |
| super.setIntArray(key, value) |
| } |
| |
| override def setBoolean(key: String, value: Boolean): Unit = this.synchronized { |
| if (value != getBoolean(key)) delta.setBoolean(key, value) |
| super.setBoolean(key, value) |
| } |
| } |
| |
| protected val synchronizedData = new SynchronizedData() |
| |
| } |