blob: b1e42749dad3681cdd6245a9dbee380eaabc5cec [file] [log] [blame] [raw]
package li.cil.oc.common.item
import java.util.UUID
import java.util.concurrent.Callable
import java.util.concurrent.TimeUnit
import com.google.common.cache.CacheBuilder
import com.google.common.cache.RemovalListener
import com.google.common.cache.RemovalNotification
import cpw.mods.fml.common.eventhandler.SubscribeEvent
import cpw.mods.fml.common.gameevent.TickEvent.ClientTickEvent
import cpw.mods.fml.common.gameevent.TickEvent.ServerTickEvent
import cpw.mods.fml.relauncher.Side
import cpw.mods.fml.relauncher.SideOnly
import li.cil.oc.Localization
import li.cil.oc.OpenComputers
import li.cil.oc.Settings
import li.cil.oc.api
import li.cil.oc.api.Driver
import li.cil.oc.api.Machine
import li.cil.oc.api.driver.EnvironmentHost
import li.cil.oc.api.machine.Architecture
import li.cil.oc.api.machine.MachineHost
import li.cil.oc.api.network.Message
import li.cil.oc.api.network.Node
import li.cil.oc.api.tileentity.Rotatable
import li.cil.oc.common.GuiType
import li.cil.oc.common.Slot
import li.cil.oc.common.inventory.ComponentInventory
import li.cil.oc.server.component
import li.cil.oc.util.ExtendedNBT._
import li.cil.oc.util.ItemUtils
import li.cil.oc.util.ItemUtils.TabletData
import li.cil.oc.util.RotationHelper
import net.minecraft.entity.Entity
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.item.ItemStack
import net.minecraft.nbt.NBTTagCompound
import net.minecraft.world.World
import net.minecraftforge.common.util.ForgeDirection
import net.minecraftforge.event.world.WorldEvent
import scala.collection.convert.WrapAsScala._
class Tablet(val parent: Delegator) extends Delegate {
// Must be assembled to be usable so we hide it in the item list.
showInItemList = false
override def maxStackSize = 1
private var iconOn: Option[Icon] = None
private var iconOff: Option[Icon] = None
@SideOnly(Side.CLIENT)
override def icon(stack: ItemStack, pass: Int) = {
if (stack.hasTagCompound) {
val data = new ItemUtils.TabletData(stack)
if (data.isRunning) iconOn else iconOff
} else super.icon(stack, pass)
}
override def registerIcons(iconRegister: IconRegister) = {
super.registerIcons(iconRegister)
iconOn = Option(iconRegister.registerIcon(Settings.resourceDomain + ":TabletOn"))
iconOff = Option(iconRegister.registerIcon(Settings.resourceDomain + ":TabletOff"))
}
// ----------------------------------------------------------------------- //
override def isDamageable = true
override def damage(stack: ItemStack) = {
val nbt = stack.getTagCompound
if (nbt != null) {
val data = new ItemUtils.TabletData()
data.load(nbt)
(data.maxEnergy - data.energy).toInt
}
else 100
}
override def maxDamage(stack: ItemStack) = {
val nbt = stack.getTagCompound
if (nbt != null) {
val data = new ItemUtils.TabletData()
data.load(nbt)
data.maxEnergy.toInt max 1
}
else 100
}
// ----------------------------------------------------------------------- //
override def update(stack: ItemStack, world: World, entity: Entity, slot: Int, selected: Boolean) =
entity match {
case player: EntityPlayer => Tablet.get(stack, player).update(world, player, slot, selected)
case _ =>
}
override def onItemRightClick(stack: ItemStack, world: World, player: EntityPlayer) = {
if (!player.isSneaking) {
if (world.isRemote) {
player.openGui(OpenComputers, GuiType.Tablet.id, world, 0, 0, 0)
}
else {
val computer = Tablet.get(stack, player).machine
computer.start()
computer.lastError match {
case message if message != null => player.addChatMessage(Localization.Analyzer.LastError(message))
case _ =>
}
}
}
else if (!world.isRemote) Tablet.Server.get(stack, player).machine.stop()
player.swingItem()
stack
}
}
class TabletWrapper(var stack: ItemStack, var holder: EntityPlayer) extends ComponentInventory with EnvironmentHost with MachineHost with Rotatable {
lazy val machine = if (holder.worldObj.isRemote) null else Machine.create(this)
val data = new TabletData()
val tablet = if (holder.worldObj.isRemote) null else new component.Tablet(this)
private var isInitialized = !world.isRemote
def items = data.items
override def facing = RotationHelper.fromYaw(holder.rotationYaw)
override def toLocal(value: ForgeDirection) = value // TODO do we care?
override def toGlobal(value: ForgeDirection) = value // TODO do we care?
def readFromNBT() {
if (stack.hasTagCompound) {
val data = stack.getTagCompound
load(data)
if (!world.isRemote) {
tablet.load(data.getCompoundTag(Settings.namespace + "component"))
machine.load(data.getCompoundTag(Settings.namespace + "data"))
}
}
}
def writeToNBT() {
if (!stack.hasTagCompound) {
stack.setTagCompound(new NBTTagCompound())
}
val data = stack.getTagCompound
if (!world.isRemote) {
if (!data.hasKey(Settings.namespace + "data")) {
data.setTag(Settings.namespace + "data", new NBTTagCompound())
}
data.setNewCompoundTag(Settings.namespace + "component", tablet.save)
data.setNewCompoundTag(Settings.namespace + "data", machine.save)
// Force tablets into stopped state to avoid errors when trying to
// load deleted machine states.
data.getCompoundTag(Settings.namespace + "data").removeTag("state")
}
save(data)
}
readFromNBT()
if (!world.isRemote) {
api.Network.joinNewNetwork(machine.node)
machine.stop()
val charge = math.max(0, this.data.energy - tablet.node.globalBuffer)
tablet.node.changeBuffer(charge)
writeToNBT()
}
// ----------------------------------------------------------------------- //
override def onConnect(node: Node) {
if (node == this.node) {
connectComponents()
node.connect(tablet.node)
}
else node.host match {
case buffer: api.component.TextBuffer =>
buffer.setMaximumColorDepth(api.component.TextBuffer.ColorDepth.FourBit)
buffer.setMaximumResolution(80, 25)
case _ =>
}
}
override protected def connectItemNode(node: Node) {
super.connectItemNode(node)
if (node != null) node.host match {
case buffer: api.component.TextBuffer => components collect {
case Some(keyboard: api.component.Keyboard) => buffer.node.connect(keyboard.node)
}
case keyboard: api.component.Keyboard => components collect {
case Some(buffer: api.component.TextBuffer) => keyboard.node.connect(buffer.node)
}
case _ =>
}
}
override def onDisconnect(node: Node) {
if (node == this.node) {
disconnectComponents()
tablet.node.remove()
}
}
override def onMessage(message: Message) {}
override def host = this
override def getSizeInventory = items.length
override def isItemValidForSlot(slot: Int, stack: ItemStack) = true
override def isUseableByPlayer(player: EntityPlayer) = machine.canInteract(player.getCommandSenderName)
override def markDirty() {}
// ----------------------------------------------------------------------- //
override def xPosition = holder.posX
override def yPosition = holder.posY + holder.getEyeHeight
override def zPosition = holder.posZ
override def world = holder.worldObj
override def markChanged(): Unit = {
}
// ----------------------------------------------------------------------- //
override def cpuArchitecture: Class[_ <: Architecture] = {
for (i <- 0 until getSizeInventory if isComponentSlot(i)) Option(getStackInSlot(i)) match {
case Some(s) => Option(Driver.driverFor(s, getClass)) match {
case Some(driver: api.driver.item.Processor) if driver.slot(s) == Slot.CPU => return driver.architecture(s)
case _ =>
}
case _ =>
}
null
}
override def installedMemory = items.foldLeft(0)((acc, itemOption) => acc + (itemOption match {
case Some(item) => Option(api.Driver.driverFor(item, getClass)) match {
case Some(driver: api.driver.item.Memory) => driver.amount(item)
case _ => 0
}
case _ => 0
}))
override def maxComponents = 32
override def componentSlot(address: String) = components.indexWhere(_.exists(env => env.node != null && env.node.address == address))
override def markForSaving() {}
override def onMachineConnect(node: Node) = onConnect(node)
override def onMachineDisconnect(node: Node) = onDisconnect(node)
// ----------------------------------------------------------------------- //
override def node = Option(machine).fold(null: Node)(_.node)
// ----------------------------------------------------------------------- //
def update(world: World, player: EntityPlayer, slot: Int, selected: Boolean) {
holder = player
if (!isInitialized) {
isInitialized = true
// This delayed initialization on the client side is required to allow
// the server to set up the tablet wrapper first (since packets generated
// in the component setup would otherwise be queued before the events that
// caused this wrapper's initialization).
connectComponents()
components collect {
case Some(buffer: api.component.TextBuffer) =>
buffer.setMaximumColorDepth(api.component.TextBuffer.ColorDepth.FourBit)
buffer.setMaximumResolution(80, 25)
}
}
if (!world.isRemote) {
machine.update()
updateComponents()
data.isRunning = machine.isRunning
data.energy = tablet.node.globalBuffer()
data.maxEnergy = tablet.node.globalBufferSize()
}
}
// ----------------------------------------------------------------------- //
override def load(nbt: NBTTagCompound) {
data.load(nbt)
}
override def save(nbt: NBTTagCompound) {
saveComponents()
data.save(nbt)
}
}
object Tablet {
def getId(stack: ItemStack) = {
if (!stack.hasTagCompound) {
stack.setTagCompound(new NBTTagCompound())
}
if (!stack.getTagCompound.hasKey(Settings.namespace + "tablet")) {
stack.getTagCompound.setString(Settings.namespace + "tablet", UUID.randomUUID().toString)
}
stack.getTagCompound.getString(Settings.namespace + "tablet")
}
def get(stack: ItemStack, holder: EntityPlayer) = {
if (holder.worldObj.isRemote) Client.get(stack, holder)
else Server.get(stack, holder)
}
@SubscribeEvent
def onWorldSave(e: WorldEvent.Save) {
Server.saveAll(e.world)
}
@SubscribeEvent
def onWorldUnload(e: WorldEvent.Unload) {
Client.clear()
Server.clear()
}
@SubscribeEvent
def onClientTick(e: ClientTickEvent) {
Client.cleanUp()
}
@SubscribeEvent
def onServerTick(e: ServerTickEvent) {
Server.cleanUp()
}
abstract class Cache extends Callable[TabletWrapper] with RemovalListener[String, TabletWrapper] {
val cache = com.google.common.cache.CacheBuilder.newBuilder().
expireAfterAccess(timeout, TimeUnit.SECONDS).
removalListener(this).
asInstanceOf[CacheBuilder[String, TabletWrapper]].
build[String, TabletWrapper]()
protected def timeout = 10
// To allow access in cache entry init.
private var currentStack: ItemStack = _
private var currentHolder: EntityPlayer = _
def get(stack: ItemStack, holder: EntityPlayer) = {
val id = getId(stack)
cache.synchronized {
currentStack = stack
currentHolder = holder
val wrapper = cache.get(id, this)
wrapper.stack = stack
wrapper.holder = holder
wrapper
}
}
def call = {
new TabletWrapper(currentStack, currentHolder)
}
def onRemoval(e: RemovalNotification[String, TabletWrapper]) {
val tablet = e.getValue
if (tablet.node != null) {
// Server.
tablet.writeToNBT()
tablet.machine.stop()
for (node <- tablet.machine.node.network.nodes) {
node.remove()
}
tablet.writeToNBT()
}
}
def clear() {
cache.synchronized {
cache.invalidateAll()
cache.cleanUp()
}
}
def cleanUp() {
cache.synchronized(cache.cleanUp())
}
}
object Client extends Cache {
override protected def timeout = 5
def get(stack: ItemStack) = {
if (stack.hasTagCompound && stack.getTagCompound.hasKey(Settings.namespace + "tablet")) {
val id = stack.getTagCompound.getString(Settings.namespace + "tablet")
cache.synchronized(Option(cache.getIfPresent(id)))
}
else None
}
}
object Server extends Cache {
def saveAll(world: World) {
cache.synchronized {
for (tablet <- cache.asMap.values if tablet.world == world) {
tablet.writeToNBT()
}
}
}
}
}