From e4db950a1713af58a6ac7171891011fb81c2a70f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Tue, 23 Sep 2014 17:38:47 +0200 Subject: [PATCH] Tablets now consume energy. Cleaned up and stabilized tablets some more. Tablets now do *not* keep running across saves (unload/reload). This was causing too many issues when they were left in running state in inventories / lying around, and would have failed anyway if the world was saved while the owning player was not in the game (which happens all the time on servers, after all). --- .../scala/li/cil/oc/common/SaveHandler.scala | 6 +- .../cil/oc/common/component/TextBuffer.scala | 11 +- .../scala/li/cil/oc/common/item/Tablet.scala | 134 ++++++++++++------ .../li/cil/oc/server/component/Tablet.scala | 19 +++ .../oc/server/component/machine/Machine.scala | 2 +- src/main/scala/li/cil/oc/util/ItemUtils.scala | 9 ++ 6 files changed, 127 insertions(+), 54 deletions(-) create mode 100644 src/main/scala/li/cil/oc/server/component/Tablet.scala diff --git a/src/main/scala/li/cil/oc/common/SaveHandler.scala b/src/main/scala/li/cil/oc/common/SaveHandler.scala index 89406e80c..6d7aa69f4 100644 --- a/src/main/scala/li/cil/oc/common/SaveHandler.scala +++ b/src/main/scala/li/cil/oc/common/SaveHandler.scala @@ -12,7 +12,7 @@ import li.cil.oc.{OpenComputers, Settings} import net.minecraft.nbt.{CompressedStreamTools, NBTTagCompound} import net.minecraft.world.{ChunkCoordIntPair, World} import net.minecraftforge.common.DimensionManager -import net.minecraftforge.event.ForgeSubscribe +import net.minecraftforge.event.{EventPriority, ForgeSubscribe} import net.minecraftforge.event.world.{ChunkDataEvent, WorldEvent} import org.apache.commons.lang3.{JavaVersion, SystemUtils} @@ -177,7 +177,7 @@ object SaveHandler { } } - @ForgeSubscribe + @ForgeSubscribe(priority = EventPriority.HIGHEST) def onWorldLoad(e: WorldEvent.Load) { // Touch all externally saved data when loading, to avoid it getting // deleted in the next save (because the now - save time will usually @@ -196,7 +196,7 @@ object SaveHandler { recurse(statePath) } - @ForgeSubscribe + @ForgeSubscribe(priority = EventPriority.LOWEST) def onWorldSave(e: WorldEvent.Save) { saveData.synchronized { saveData.get(e.world.provider.dimensionId) match { diff --git a/src/main/scala/li/cil/oc/common/component/TextBuffer.scala b/src/main/scala/li/cil/oc/common/component/TextBuffer.scala index ddd052f30..fdc4fff0f 100644 --- a/src/main/scala/li/cil/oc/common/component/TextBuffer.scala +++ b/src/main/scala/li/cil/oc/common/component/TextBuffer.scala @@ -188,11 +188,12 @@ class TextBuffer(val owner: Container) extends ManagedComponent with api.compone val (mw, mh) = maxResolution if (w < 1 || h < 1 || w > mw || h > mw || h * w > mw * mh) throw new IllegalArgumentException("unsupported resolution") + // Always send to clients, their state might be dirty. + proxy.onScreenResolutionChange(w, h) if (data.size = (w, h)) { if (node != null) { node.sendToReachable("computer.signal", "screen_resized", Int.box(w), Int.box(h)) } - proxy.onScreenResolutionChange(w, h) true } else false @@ -209,11 +210,9 @@ class TextBuffer(val owner: Container) extends ManagedComponent with api.compone override def setColorDepth(depth: ColorDepth) = { if (depth.ordinal > maxDepth.ordinal) throw new IllegalArgumentException("unsupported depth") - if (data.format = PackedColor.Depth.format(depth)) { - proxy.onScreenDepthChange(depth) - true - } - else false + // Always send to clients, their state might be dirty. + proxy.onScreenDepthChange(depth) + data.format = PackedColor.Depth.format(depth) } override def getColorDepth = data.format.depth diff --git a/src/main/scala/li/cil/oc/common/item/Tablet.scala b/src/main/scala/li/cil/oc/common/item/Tablet.scala index 2cd6d0597..b0f35ca7f 100644 --- a/src/main/scala/li/cil/oc/common/item/Tablet.scala +++ b/src/main/scala/li/cil/oc/common/item/Tablet.scala @@ -9,22 +9,25 @@ import cpw.mods.fml.common.{ITickHandler, TickType} import cpw.mods.fml.relauncher.{Side, SideOnly} import li.cil.oc.api.driver.Container import li.cil.oc.api.machine.Owner -import li.cil.oc.api.network.{Connector, Message, Node} +import li.cil.oc.api.network.{Message, Node} import li.cil.oc.api.{Machine, Rotatable} import li.cil.oc.common.GuiType import li.cil.oc.common.inventory.ComponentInventory +import li.cil.oc.server.component import li.cil.oc.util.ItemUtils.TabletData -import li.cil.oc.util.RotationHelper +import li.cil.oc.util.{ItemUtils, RotationHelper} import li.cil.oc.{OpenComputers, Settings, api} import net.minecraft.entity.Entity import net.minecraft.entity.player.EntityPlayer import net.minecraft.item.ItemStack -import net.minecraft.nbt.{NBTTagCompound, NBTTagInt} +import net.minecraft.nbt.NBTTagCompound import net.minecraft.world.World import net.minecraftforge.common.ForgeDirection import net.minecraftforge.event.ForgeSubscribe 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 @@ -35,9 +38,14 @@ class Tablet(val parent: Delegator) extends Delegate { private var iconOff: Option[Icon] = None @SideOnly(Side.CLIENT) - override def icon(stack: ItemStack, pass: Int) = Tablet.Client.get(stack) match { - case Some(wrapper) => if (wrapper.isRunning) iconOn else iconOff - case _ => super.icon(stack, pass) + override def icon(stack: ItemStack, pass: Int) = { + val nbt = stack.getTagCompound + if (nbt != null) { + val data = new ItemUtils.TabletData() + data.load(nbt) + if (data.isRunning) iconOn else iconOff + } + else super.icon(stack, pass) } override def registerIcons(iconRegister: IconRegister) = { @@ -47,6 +55,32 @@ class Tablet(val parent: Delegator) extends Delegate { 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) @@ -62,10 +96,7 @@ class Tablet(val parent: Delegator) extends Delegate { Tablet.get(stack, player).start() } } - else { - if (world.isRemote) Tablet.Client.remove(stack) - else Tablet.Server.remove(stack) - } + else if (!world.isRemote) Tablet.Server.get(stack, player).computer.stop() player.swingItem() stack } @@ -76,6 +107,10 @@ class TabletWrapper(var stack: ItemStack, var holder: EntityPlayer) extends Comp 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) @@ -89,6 +124,7 @@ class TabletWrapper(var stack: ItemStack, var holder: EntityPlayer) extends Comp val data = stack.getTagCompound if (!world.isRemote) { computer.load(data.getCompoundTag(Settings.namespace + "data")) + tablet.load(data.getCompoundTag(Settings.namespace + "component")) } load(data) } @@ -104,21 +140,19 @@ class TabletWrapper(var stack: ItemStack, var holder: EntityPlayer) extends Comp data.setTag(Settings.namespace + "data", new NBTTagCompound()) } computer.save(data.getCompoundTag(Settings.namespace + "data")) + tablet.save(data.getCompoundTag(Settings.namespace + "component")) + + // 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) { - connectComponents() - components collect { - case Some(buffer: api.component.TextBuffer) => - buffer.setMaximumColorDepth(api.component.TextBuffer.ColorDepth.FourBit) - buffer.setMaximumResolution(80, 25) - } - } - else { + if (!world.isRemote) { api.Network.joinNewNetwork(computer.node) + computer.stop() writeToNBT() } @@ -127,6 +161,7 @@ class TabletWrapper(var stack: ItemStack, var holder: EntityPlayer) extends Comp override def onConnect(node: Node) { if (node == this.node) { connectComponents() + node.connect(tablet.node) } else node.host match { case buffer: api.component.TextBuffer => @@ -152,6 +187,7 @@ class TabletWrapper(var stack: ItemStack, var holder: EntityPlayer) extends Comp override def onDisconnect(node: Node) { if (node == this.node) { disconnectComponents() + tablet.node.remove() } } @@ -215,13 +251,7 @@ class TabletWrapper(var stack: ItemStack, var holder: EntityPlayer) extends Comp override def canInteract(player: String) = computer.canInteract(player) - override def isRunning = if (world.isRemote) { - import li.cil.oc.util.ExtendedNBT._ - val computerData = stack.getTagCompound.getCompoundTag(Settings.namespace + "data") - val state = computerData.getTagList("state").iterator[NBTTagInt].headOption.fold(0)(_.data) - state != 0 - } - else computer.isRunning + override def isRunning = computer.isRunning override def isPaused = computer.isPaused @@ -237,10 +267,25 @@ class TabletWrapper(var stack: ItemStack, var holder: EntityPlayer) extends Comp 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) { - computer.node.asInstanceOf[Connector].changeBuffer(500) computer.update() updateComponents() + data.isRunning = computer.isRunning + data.energy = tablet.node.globalBuffer() + data.maxEnergy = tablet.node.globalBufferSize() } } @@ -257,6 +302,17 @@ class TabletWrapper(var stack: ItemStack, var holder: EntityPlayer) extends Comp } object Tablet extends ITickHandler { + 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) @@ -286,24 +342,20 @@ object Tablet extends ITickHandler { abstract class Cache extends Callable[TabletWrapper] with RemovalListener[String, TabletWrapper] { val cache = com.google.common.cache.CacheBuilder.newBuilder(). - expireAfterAccess(10, TimeUnit.SECONDS). + 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) = { - if (!stack.hasTagCompound) { - stack.setTagCompound(new NBTTagCompound()) - } - if (!stack.getTagCompound.hasKey(Settings.namespace + "tablet")) { - stack.getTagCompound.setString(Settings.namespace + "tablet", UUID.randomUUID().toString) - } - val id = stack.getTagCompound.getString(Settings.namespace + "tablet") + val id = getId(stack) cache.synchronized { currentStack = stack currentHolder = holder @@ -321,18 +373,11 @@ object Tablet extends ITickHandler { if (tablet.node != null) { // Server. tablet.writeToNBT() - tablet.stop() + tablet.computer.stop() tablet.node.remove() } } - def remove(stack: ItemStack) { - cache.synchronized { - cache.invalidate(stack) - cache.cleanUp() - } - } - def clear() { cache.synchronized { cache.invalidateAll() @@ -346,6 +391,8 @@ object Tablet extends ITickHandler { } 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") @@ -358,7 +405,6 @@ object Tablet extends ITickHandler { object Server extends Cache { def saveAll(world: World) { cache.synchronized { - import scala.collection.convert.WrapAsScala._ for (tablet <- cache.asMap.values if tablet.world == world) { tablet.writeToNBT() } diff --git a/src/main/scala/li/cil/oc/server/component/Tablet.scala b/src/main/scala/li/cil/oc/server/component/Tablet.scala new file mode 100644 index 000000000..d184097b3 --- /dev/null +++ b/src/main/scala/li/cil/oc/server/component/Tablet.scala @@ -0,0 +1,19 @@ +package li.cil.oc.server.component + +import li.cil.oc.Settings +import li.cil.oc.api.Network +import li.cil.oc.api.network.{Arguments, Callback, Context, Visibility} +import li.cil.oc.common.component +import li.cil.oc.common.item.TabletWrapper + +class Tablet(val tablet: TabletWrapper) extends component.ManagedComponent { + val node = Network.newNode(this, Visibility.Network). + withComponent("tablet"). + withConnector(Settings.get.bufferRobot). + create() + + // ----------------------------------------------------------------------- // + + @Callback(doc = """function():boolean -- Whether the local bus interface is enabled.""") + def getPitch(context: Context, args: Arguments): Array[AnyRef] = result(tablet.holder.rotationPitch) +} diff --git a/src/main/scala/li/cil/oc/server/component/machine/Machine.scala b/src/main/scala/li/cil/oc/server/component/machine/Machine.scala index c33974c5b..fd917157d 100644 --- a/src/main/scala/li/cil/oc/server/component/machine/Machine.scala +++ b/src/main/scala/li/cil/oc/server/component/machine/Machine.scala @@ -579,7 +579,7 @@ class Machine(val owner: Owner, constructor: Constructor[_ <: Architecture]) ext else fs.load(SaveHandler.loadNBT(nbt, node.address + "_tmp")) }) - if (state.size > 0 && state.top != Machine.State.Stopped && init()) try { + if (state.size > 0 && isRunning && init()) try { architecture.load(nbt) signals ++= nbt.getTagList("signals").iterator[NBTTagCompound].map(signalNbt => { diff --git a/src/main/scala/li/cil/oc/util/ItemUtils.scala b/src/main/scala/li/cil/oc/util/ItemUtils.scala index b03f603d3..89985cea3 100644 --- a/src/main/scala/li/cil/oc/util/ItemUtils.scala +++ b/src/main/scala/li/cil/oc/util/ItemUtils.scala @@ -201,6 +201,9 @@ object ItemUtils { } var items = Array.fill[Option[ItemStack]](32)(None) + var isRunning = false + var energy = 0.0 + var maxEnergy = 0.0 override def load(nbt: NBTTagCompound) { nbt.getTagList(Settings.namespace + "items").foreach[NBTTagCompound](slotNbt => { @@ -209,6 +212,9 @@ object ItemUtils { items(slot) = Option(ItemStack.loadItemStackFromNBT(slotNbt.getCompoundTag("item"))) } }) + isRunning = nbt.getBoolean(Settings.namespace + "isRunning") + energy = nbt.getDouble(Settings.namespace + "energy") + maxEnergy = nbt.getDouble(Settings.namespace + "maxEnergy") } override def save(nbt: NBTTagCompound) { @@ -221,6 +227,9 @@ object ItemUtils { slotNbt.setByte("slot", slot.toByte) slotNbt.setNewCompoundTag("item", stack.writeToNBT) }) + nbt.setBoolean(Settings.namespace + "isRunning", isRunning) + nbt.setDouble(Settings.namespace + "energy", energy) + nbt.setDouble(Settings.namespace + "maxEnergy", maxEnergy) } }