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.nbt.{CompressedStreamTools, NBTTagCompound}
import net.minecraft.world.{ChunkCoordIntPair, World} import net.minecraft.world.{ChunkCoordIntPair, World}
import net.minecraftforge.common.DimensionManager import net.minecraftforge.common.DimensionManager
import net.minecraftforge.event.ForgeSubscribe import net.minecraftforge.event.{EventPriority, ForgeSubscribe}
import net.minecraftforge.event.world.{ChunkDataEvent, WorldEvent} import net.minecraftforge.event.world.{ChunkDataEvent, WorldEvent}
import org.apache.commons.lang3.{JavaVersion, SystemUtils} import org.apache.commons.lang3.{JavaVersion, SystemUtils}
@ -177,7 +177,7 @@ object SaveHandler {
} }
} }
@ForgeSubscribe @ForgeSubscribe(priority = EventPriority.HIGHEST)
def onWorldLoad(e: WorldEvent.Load) { def onWorldLoad(e: WorldEvent.Load) {
// Touch all externally saved data when loading, to avoid it getting // Touch all externally saved data when loading, to avoid it getting
// deleted in the next save (because the now - save time will usually // deleted in the next save (because the now - save time will usually
@ -196,7 +196,7 @@ object SaveHandler {
recurse(statePath) recurse(statePath)
} }
@ForgeSubscribe @ForgeSubscribe(priority = EventPriority.LOWEST)
def onWorldSave(e: WorldEvent.Save) { def onWorldSave(e: WorldEvent.Save) {
saveData.synchronized { saveData.synchronized {
saveData.get(e.world.provider.dimensionId) match { 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 val (mw, mh) = maxResolution
if (w < 1 || h < 1 || w > mw || h > mw || h * w > mw * mh) if (w < 1 || h < 1 || w > mw || h > mw || h * w > mw * mh)
throw new IllegalArgumentException("unsupported resolution") throw new IllegalArgumentException("unsupported resolution")
// Always send to clients, their state might be dirty.
proxy.onScreenResolutionChange(w, h)
if (data.size = (w, h)) { if (data.size = (w, h)) {
if (node != null) { if (node != null) {
node.sendToReachable("computer.signal", "screen_resized", Int.box(w), Int.box(h)) node.sendToReachable("computer.signal", "screen_resized", Int.box(w), Int.box(h))
} }
proxy.onScreenResolutionChange(w, h)
true true
} }
else false else false
@ -209,11 +210,9 @@ class TextBuffer(val owner: Container) extends ManagedComponent with api.compone
override def setColorDepth(depth: ColorDepth) = { override def setColorDepth(depth: ColorDepth) = {
if (depth.ordinal > maxDepth.ordinal) if (depth.ordinal > maxDepth.ordinal)
throw new IllegalArgumentException("unsupported depth") throw new IllegalArgumentException("unsupported depth")
if (data.format = PackedColor.Depth.format(depth)) { // Always send to clients, their state might be dirty.
proxy.onScreenDepthChange(depth) proxy.onScreenDepthChange(depth)
true data.format = PackedColor.Depth.format(depth)
}
else false
} }
override def getColorDepth = data.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 cpw.mods.fml.relauncher.{Side, SideOnly}
import li.cil.oc.api.driver.Container import li.cil.oc.api.driver.Container
import li.cil.oc.api.machine.Owner 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.api.{Machine, Rotatable}
import li.cil.oc.common.GuiType import li.cil.oc.common.GuiType
import li.cil.oc.common.inventory.ComponentInventory 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.ItemUtils.TabletData
import li.cil.oc.util.RotationHelper import li.cil.oc.util.{ItemUtils, RotationHelper}
import li.cil.oc.{OpenComputers, Settings, api} import li.cil.oc.{OpenComputers, Settings, api}
import net.minecraft.entity.Entity import net.minecraft.entity.Entity
import net.minecraft.entity.player.EntityPlayer import net.minecraft.entity.player.EntityPlayer
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
import net.minecraft.nbt.{NBTTagCompound, NBTTagInt} import net.minecraft.nbt.NBTTagCompound
import net.minecraft.world.World import net.minecraft.world.World
import net.minecraftforge.common.ForgeDirection import net.minecraftforge.common.ForgeDirection
import net.minecraftforge.event.ForgeSubscribe import net.minecraftforge.event.ForgeSubscribe
import net.minecraftforge.event.world.WorldEvent import net.minecraftforge.event.world.WorldEvent
import scala.collection.convert.WrapAsScala._
class Tablet(val parent: Delegator) extends Delegate { class Tablet(val parent: Delegator) extends Delegate {
// Must be assembled to be usable so we hide it in the item list. // Must be assembled to be usable so we hide it in the item list.
showInItemList = false showInItemList = false
@ -35,9 +38,14 @@ class Tablet(val parent: Delegator) extends Delegate {
private var iconOff: Option[Icon] = None private var iconOff: Option[Icon] = None
@SideOnly(Side.CLIENT) @SideOnly(Side.CLIENT)
override def icon(stack: ItemStack, pass: Int) = Tablet.Client.get(stack) match { override def icon(stack: ItemStack, pass: Int) = {
case Some(wrapper) => if (wrapper.isRunning) iconOn else iconOff val nbt = stack.getTagCompound
case _ => super.icon(stack, pass) 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) = { override def registerIcons(iconRegister: IconRegister) = {
@ -47,6 +55,32 @@ class Tablet(val parent: Delegator) extends Delegate {
iconOff = Option(iconRegister.registerIcon(Settings.resourceDomain + ":TabletOff")) 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) = override def update(stack: ItemStack, world: World, entity: Entity, slot: Int, selected: Boolean) =
entity match { entity match {
case player: EntityPlayer => Tablet.get(stack, player).update(world, player, slot, selected) 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() Tablet.get(stack, player).start()
} }
} }
else { else if (!world.isRemote) Tablet.Server.get(stack, player).computer.stop()
if (world.isRemote) Tablet.Client.remove(stack)
else Tablet.Server.remove(stack)
}
player.swingItem() player.swingItem()
stack stack
} }
@ -76,6 +107,10 @@ class TabletWrapper(var stack: ItemStack, var holder: EntityPlayer) extends Comp
val data = new TabletData() 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 def items = data.items
override def facing = RotationHelper.fromYaw(holder.rotationYaw) 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 val data = stack.getTagCompound
if (!world.isRemote) { if (!world.isRemote) {
computer.load(data.getCompoundTag(Settings.namespace + "data")) computer.load(data.getCompoundTag(Settings.namespace + "data"))
tablet.load(data.getCompoundTag(Settings.namespace + "component"))
} }
load(data) load(data)
} }
@ -104,21 +140,19 @@ class TabletWrapper(var stack: ItemStack, var holder: EntityPlayer) extends Comp
data.setTag(Settings.namespace + "data", new NBTTagCompound()) data.setTag(Settings.namespace + "data", new NBTTagCompound())
} }
computer.save(data.getCompoundTag(Settings.namespace + "data")) 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) save(data)
} }
readFromNBT() readFromNBT()
if (world.isRemote) { if (!world.isRemote) {
connectComponents()
components collect {
case Some(buffer: api.component.TextBuffer) =>
buffer.setMaximumColorDepth(api.component.TextBuffer.ColorDepth.FourBit)
buffer.setMaximumResolution(80, 25)
}
}
else {
api.Network.joinNewNetwork(computer.node) api.Network.joinNewNetwork(computer.node)
computer.stop()
writeToNBT() writeToNBT()
} }
@ -127,6 +161,7 @@ class TabletWrapper(var stack: ItemStack, var holder: EntityPlayer) extends Comp
override def onConnect(node: Node) { override def onConnect(node: Node) {
if (node == this.node) { if (node == this.node) {
connectComponents() connectComponents()
node.connect(tablet.node)
} }
else node.host match { else node.host match {
case buffer: api.component.TextBuffer => case buffer: api.component.TextBuffer =>
@ -152,6 +187,7 @@ class TabletWrapper(var stack: ItemStack, var holder: EntityPlayer) extends Comp
override def onDisconnect(node: Node) { override def onDisconnect(node: Node) {
if (node == this.node) { if (node == this.node) {
disconnectComponents() 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 canInteract(player: String) = computer.canInteract(player)
override def isRunning = if (world.isRemote) { override def isRunning = computer.isRunning
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 isPaused = computer.isPaused 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) { def update(world: World, player: EntityPlayer, slot: Int, selected: Boolean) {
holder = player 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) { if (!world.isRemote) {
computer.node.asInstanceOf[Connector].changeBuffer(500)
computer.update() computer.update()
updateComponents() 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 { 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) = { def get(stack: ItemStack, holder: EntityPlayer) = {
if (holder.worldObj.isRemote) Client.get(stack, holder) if (holder.worldObj.isRemote) Client.get(stack, holder)
else Server.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] { abstract class Cache extends Callable[TabletWrapper] with RemovalListener[String, TabletWrapper] {
val cache = com.google.common.cache.CacheBuilder.newBuilder(). val cache = com.google.common.cache.CacheBuilder.newBuilder().
expireAfterAccess(10, TimeUnit.SECONDS). expireAfterAccess(timeout, TimeUnit.SECONDS).
removalListener(this). removalListener(this).
asInstanceOf[CacheBuilder[String, TabletWrapper]]. asInstanceOf[CacheBuilder[String, TabletWrapper]].
build[String, TabletWrapper]() build[String, TabletWrapper]()
protected def timeout = 10
// To allow access in cache entry init. // To allow access in cache entry init.
private var currentStack: ItemStack = _ private var currentStack: ItemStack = _
private var currentHolder: EntityPlayer = _ private var currentHolder: EntityPlayer = _
def get(stack: ItemStack, holder: EntityPlayer) = { def get(stack: ItemStack, holder: EntityPlayer) = {
if (!stack.hasTagCompound) { val id = getId(stack)
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")
cache.synchronized { cache.synchronized {
currentStack = stack currentStack = stack
currentHolder = holder currentHolder = holder
@ -321,18 +373,11 @@ object Tablet extends ITickHandler {
if (tablet.node != null) { if (tablet.node != null) {
// Server. // Server.
tablet.writeToNBT() tablet.writeToNBT()
tablet.stop() tablet.computer.stop()
tablet.node.remove() tablet.node.remove()
} }
} }
def remove(stack: ItemStack) {
cache.synchronized {
cache.invalidate(stack)
cache.cleanUp()
}
}
def clear() { def clear() {
cache.synchronized { cache.synchronized {
cache.invalidateAll() cache.invalidateAll()
@ -346,6 +391,8 @@ object Tablet extends ITickHandler {
} }
object Client extends Cache { object Client extends Cache {
override protected def timeout = 5
def get(stack: ItemStack) = { def get(stack: ItemStack) = {
if (stack.hasTagCompound && stack.getTagCompound.hasKey(Settings.namespace + "tablet")) { if (stack.hasTagCompound && stack.getTagCompound.hasKey(Settings.namespace + "tablet")) {
val id = stack.getTagCompound.getString(Settings.namespace + "tablet") val id = stack.getTagCompound.getString(Settings.namespace + "tablet")
@ -358,7 +405,6 @@ object Tablet extends ITickHandler {
object Server extends Cache { object Server extends Cache {
def saveAll(world: World) { def saveAll(world: World) {
cache.synchronized { cache.synchronized {
import scala.collection.convert.WrapAsScala._
for (tablet <- cache.asMap.values if tablet.world == world) { for (tablet <- cache.asMap.values if tablet.world == world) {
tablet.writeToNBT() 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")) 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) architecture.load(nbt)
signals ++= nbt.getTagList("signals").iterator[NBTTagCompound].map(signalNbt => { 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 items = Array.fill[Option[ItemStack]](32)(None)
var isRunning = false
var energy = 0.0
var maxEnergy = 0.0
override def load(nbt: NBTTagCompound) { override def load(nbt: NBTTagCompound) {
nbt.getTagList(Settings.namespace + "items").foreach[NBTTagCompound](slotNbt => { nbt.getTagList(Settings.namespace + "items").foreach[NBTTagCompound](slotNbt => {
@ -209,6 +212,9 @@ object ItemUtils {
items(slot) = Option(ItemStack.loadItemStackFromNBT(slotNbt.getCompoundTag("item"))) 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) { override def save(nbt: NBTTagCompound) {
@ -221,6 +227,9 @@ object ItemUtils {
slotNbt.setByte("slot", slot.toByte) slotNbt.setByte("slot", slot.toByte)
slotNbt.setNewCompoundTag("item", stack.writeToNBT) slotNbt.setNewCompoundTag("item", stack.writeToNBT)
}) })
nbt.setBoolean(Settings.namespace + "isRunning", isRunning)
nbt.setDouble(Settings.namespace + "energy", energy)
nbt.setDouble(Settings.namespace + "maxEnergy", maxEnergy)
} }
} }