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).
This commit is contained in:
Florian Nücke 2014-09-23 17:38:47 +02:00
parent 65a0327695
commit e4db950a17
6 changed files with 127 additions and 54 deletions

View File

@ -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 {

View File

@ -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

View File

@ -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()
}

View File

@ -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)
}

View File

@ -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 => {

View File

@ -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)
}
}