diff --git a/assets/opencomputers/textures/items/upgrade_generator_equipped.png b/assets/opencomputers/textures/items/upgrade_generator_equipped.png index 7c810babf..abbf62d32 100644 Binary files a/assets/opencomputers/textures/items/upgrade_generator_equipped.png and b/assets/opencomputers/textures/items/upgrade_generator_equipped.png differ diff --git a/li/cil/oc/api/network/RobotContext.java b/li/cil/oc/api/network/RobotContext.java index 6289d1247..6e72bbee8 100644 --- a/li/cil/oc/api/network/RobotContext.java +++ b/li/cil/oc/api/network/RobotContext.java @@ -27,4 +27,17 @@ public interface RobotContext extends Context { * @return the fake player for the robot. */ EntityPlayer player(); + + /** + * Causes the currently installed upgrade to be saved and synchronized. + *
+ * If no upgrade is installed in the robot this does nothing. + * + * This is intended for upgrade components, to allow them to update their + * client side representation for rendering purposes. The component will be + * saved to its item's NBT tag compound, as it would be when the game is + * saved, and then re-sent to the client. Keep the number of calls to this + * function low, since each call causes a network packet to be sent. + */ + void saveUpgrade(); } diff --git a/li/cil/oc/client/PacketHandler.scala b/li/cil/oc/client/PacketHandler.scala index 8c6168dd6..4f2ce836c 100644 --- a/li/cil/oc/client/PacketHandler.scala +++ b/li/cil/oc/client/PacketHandler.scala @@ -73,7 +73,7 @@ class PacketHandler extends CommonPacketHandler { def onComputerState(p: PacketParser) = p.readTileEntity[Computer]() match { - case Some(t) => t.isRunning = p.readBoolean() + case Some(t) => t.isRunning_=(p.readBoolean()) case _ => // Invalid packet. } diff --git a/li/cil/oc/client/renderer/item/UpgradeRenderer.scala b/li/cil/oc/client/renderer/item/UpgradeRenderer.scala index 32d110fa0..c80bcf60b 100644 --- a/li/cil/oc/client/renderer/item/UpgradeRenderer.scala +++ b/li/cil/oc/client/renderer/item/UpgradeRenderer.scala @@ -1,5 +1,6 @@ package li.cil.oc.client.renderer.item +import li.cil.oc.server.driver.item.Item import li.cil.oc.{Settings, Items} import net.minecraft.client.Minecraft import net.minecraft.client.renderer.Tessellator @@ -17,17 +18,22 @@ object UpgradeRenderer extends IItemRenderer { } } - def shouldUseRenderHelper(renderType: ItemRenderType, item: ItemStack, helper: ItemRendererHelper) = helper == ItemRendererHelper.EQUIPPED_BLOCK + def shouldUseRenderHelper(renderType: ItemRenderType, stack: ItemStack, helper: ItemRendererHelper) = + // Note: it's easier to revert changes introduced by this "helper" than by + // the code that applies if no helper is used... + helper == ItemRendererHelper.EQUIPPED_BLOCK - def renderItem(renderType: ItemRenderType, item: ItemStack, data: AnyRef*) { + def renderItem(renderType: ItemRenderType, stack: ItemStack, data: AnyRef*) { val tm = Minecraft.getMinecraft.getTextureManager - GL11.glTranslatef(0.5f, 0.5f, 0.5f) val t = Tessellator.instance - Items.multi.subItem(item) match { + // Revert offset introduced by the render "helper". + GL11.glTranslatef(0.5f, 0.5f, 0.5f) + + Items.multi.subItem(stack) match { case Some(subItem) if subItem == Items.generator => // TODO display lists? - val onOffset = if (true) 0.5 else 0 + val onOffset = if (Item.dataTag(stack).getInteger("remainingTicks") > 0) 0.5 else 0 val b = AxisAlignedBB.getAABBPool.getAABB(0.4, 0.2, 0.16, 0.6, 0.4, 0.36) tm.bindTexture(new ResourceLocation(Settings.resourceDomain, "textures/items/upgrade_generator_equipped.png")) diff --git a/li/cil/oc/common/tileentity/ComponentInventory.scala b/li/cil/oc/common/tileentity/ComponentInventory.scala index af45b2327..1a957b3ca 100644 --- a/li/cil/oc/common/tileentity/ComponentInventory.scala +++ b/li/cil/oc/common/tileentity/ComponentInventory.scala @@ -86,7 +86,7 @@ trait ComponentInventory extends Inventory with network.Environment { self: MCTi override protected def onItemAdded(slot: Int, stack: ItemStack) = if (isServer && isComponentSlot(slot)) { Registry.driverFor(stack) match { case Some(driver) => Option(driver.createEnvironment(stack, this)) match { - case Some(component) => + case Some(component) => this.synchronized { components(slot) = Some(component) component.load(dataTag(driver, stack)) connectItemNode(component.node) @@ -95,6 +95,7 @@ trait ComponentInventory extends Inventory with network.Environment { self: MCTi updatingComponents += component } component.save(dataTag(driver, stack)) + } case _ => // No environment (e.g. RAM). } case _ => // No driver. @@ -104,7 +105,7 @@ trait ComponentInventory extends Inventory with network.Environment { self: MCTi override protected def onItemRemoved(slot: Int, stack: ItemStack) = if (isServer) { // Uninstall component previously in that slot. components(slot) match { - case Some(component) => + case Some(component) => this.synchronized { // Note to self: we have to remove the node from the network *before* // saving, to allow file systems to close their handles before they // are saved (otherwise hard drives would restore all handles after @@ -114,6 +115,7 @@ trait ComponentInventory extends Inventory with network.Environment { self: MCTi component.node.remove() Registry.driverFor(stack).foreach(driver => component.save(dataTag(driver, stack))) + } case _ => // Nothing to do. } } @@ -124,6 +126,6 @@ trait ComponentInventory extends Inventory with network.Environment { self: MCTi this.node.connect(node) } - private def dataTag(driver: ItemDriver, stack: ItemStack) = + protected def dataTag(driver: ItemDriver, stack: ItemStack) = Option(driver.dataTag(stack)).getOrElse(Item.dataTag(stack)) } \ No newline at end of file diff --git a/li/cil/oc/common/tileentity/Computer.scala b/li/cil/oc/common/tileentity/Computer.scala index 34d84b7d5..bba3f3f8f 100644 --- a/li/cil/oc/common/tileentity/Computer.scala +++ b/li/cil/oc/common/tileentity/Computer.scala @@ -11,7 +11,7 @@ import net.minecraftforge.common.ForgeDirection import scala.Some import scala.collection.mutable -abstract class Computer(isRemote: Boolean) extends Environment with ComponentInventory with Rotatable with BundledRedstoneAware with Analyzable { +abstract class Computer(isRemote: Boolean) extends Environment with ComponentInventory with Rotatable with BundledRedstoneAware with Analyzable with Context { protected val _computer = if (isRemote) null else new component.Computer(this) def computer = _computer @@ -28,6 +28,16 @@ abstract class Computer(isRemote: Boolean) extends Environment with ComponentInv // ----------------------------------------------------------------------- // + // Note: we implement IContext in the TE to allow external components to cast + // their owner to it (to allow interacting with their owning computer). + + def address() = computer.address + + def canInteract(player: String) = + if (isServer) computer.canInteract(player) + else !Settings.get.canComputersBeOwned || + _users.isEmpty || _users.contains(player) + def isRunning = _isRunning @SideOnly(Side.CLIENT) @@ -37,6 +47,18 @@ abstract class Computer(isRemote: Boolean) extends Environment with ComponentInv this } + def isPaused = computer.isPaused + + def start() = computer.start() + + def pause(seconds: Double) = computer.pause(seconds) + + def stop() = computer.stop() + + def signal(name: String, args: AnyRef*) = computer.signal(name, args: _*) + + // ----------------------------------------------------------------------- // + def markAsChanged() = hasChanged = true def hasRedstoneCard = items.exists { @@ -54,11 +76,6 @@ abstract class Computer(isRemote: Boolean) extends Environment with ComponentInv _users ++= list } - def canInteract(player: String) = - if (isServer) computer.canInteract(player) - else !Settings.get.canComputersBeOwned || - _users.isEmpty || _users.contains(player) - // ----------------------------------------------------------------------- // override def updateEntity() { @@ -110,7 +127,7 @@ abstract class Computer(isRemote: Boolean) extends Environment with ComponentInv @SideOnly(Side.CLIENT) override def readFromNBTForClient(nbt: NBTTagCompound) { super.readFromNBTForClient(nbt) - isRunning = nbt.getBoolean("isRunning") + isRunning_=(nbt.getBoolean("isRunning")) _users.clear() _users ++= nbt.getTagList("users").iterator[NBTTagString].map(_.data) } diff --git a/li/cil/oc/common/tileentity/Robot.scala b/li/cil/oc/common/tileentity/Robot.scala index dc305e119..bfaa953fe 100644 --- a/li/cil/oc/common/tileentity/Robot.scala +++ b/li/cil/oc/common/tileentity/Robot.scala @@ -24,13 +24,32 @@ import scala.Some // robot moves we only create a new proxy tile entity, hook the instance of this // class that was held by the old proxy to it and can then safely forget the // old proxy, which will be cleaned up by Minecraft like any other tile entity. -class Robot(isRemote: Boolean) extends Computer(isRemote) with ISidedInventory with Buffer with PowerInformation { +class Robot(isRemote: Boolean) extends Computer(isRemote) with ISidedInventory with Buffer with PowerInformation with RobotContext { def this() = this(false) var proxy: RobotProxy = _ // ----------------------------------------------------------------------- // + // Note: we implement IRobotContext in the TE to allow external components + //to cast their owner to it (to allow interacting with their owning robot). + + var selectedSlot = actualSlot(0) + + def player() = player(facing, facing) + + def saveUpgrade() = this.synchronized { + components(3) match { + case Some(environment) => + val stack = getStackInSlot(3) + // We're guaranteed to have a driver for entries. + environment.save(dataTag(Registry.driverFor(stack).get, stack)) + ServerPacketSender.sendRobotEquippedUpgradeChange(this, stack) + } + } + + // ----------------------------------------------------------------------- // + override def node = if (isClient) null else computer.node override val _buffer = new common.component.Buffer(this) { @@ -62,8 +81,6 @@ class Robot(isRemote: Boolean) extends Computer(isRemote) with ISidedInventory w var globalBuffer, globalBufferSize = 0.0 - var selectedSlot = actualSlot(0) - var equippedItem: Option[ItemStack] = None var equippedUpgrade: Option[ItemStack] = None @@ -309,7 +326,9 @@ class Robot(isRemote: Boolean) extends Computer(isRemote) with ISidedInventory w } } - override def writeToNBT(nbt: NBTTagCompound) { + override def writeToNBT(nbt: NBTTagCompound) = this.synchronized { + // Note: computer is saved when proxy is saved (in proxy's super writeToNBT) + // which is a bit ugly, and may be refactored some day, but it works. nbt.setNewCompoundTag(Settings.namespace + "buffer", buffer.save) nbt.setNewCompoundTag(Settings.namespace + "gpu", gpu.save) nbt.setNewCompoundTag(Settings.namespace + "keyboard", keyboard.save) @@ -350,13 +369,22 @@ class Robot(isRemote: Boolean) extends Computer(isRemote) with ISidedInventory w } } - override def writeToNBTForClient(nbt: NBTTagCompound) { + override def writeToNBTForClient(nbt: NBTTagCompound) = this.synchronized { super.writeToNBTForClient(nbt) nbt.setInteger("selectedSlot", selectedSlot) if (getStackInSlot(0) != null) { nbt.setNewCompoundTag("equipped", getStackInSlot(0).writeToNBT) } if (getStackInSlot(3) != null) { + // Force saving to item's NBT if necessary before sending, to make sure + // we transfer the component's current state (e.g. running or not for + // generator upgrades). + components(3) match { + case Some(environment) => + val stack = getStackInSlot(3) + // We're guaranteed to have a driver for entries. + environment.save(dataTag(Registry.driverFor(stack).get, stack)) + } nbt.setNewCompoundTag("upgrade", getStackInSlot(3).writeToNBT) } nbt.setDouble(Settings.namespace + "xp", xp) diff --git a/li/cil/oc/common/tileentity/RobotProxy.scala b/li/cil/oc/common/tileentity/RobotProxy.scala index 46cb1bd07..6d1f866f3 100644 --- a/li/cil/oc/common/tileentity/RobotProxy.scala +++ b/li/cil/oc/common/tileentity/RobotProxy.scala @@ -14,7 +14,7 @@ import net.minecraft.item.ItemStack import net.minecraft.nbt.NBTTagCompound import net.minecraftforge.common.ForgeDirection -class RobotProxy(val robot: Robot) extends Computer(robot.isClient) with ISidedInventory with Buffer with PowerInformation { +class RobotProxy(val robot: Robot) extends Computer(robot.isClient) with ISidedInventory with Buffer with PowerInformation with RobotContext { def this() = this(new Robot(false)) // ----------------------------------------------------------------------- // @@ -23,6 +23,25 @@ class RobotProxy(val robot: Robot) extends Computer(robot.isClient) with ISidedI withComponent("robot", Visibility.Neighbors). create() + override protected val _computer = null + + override def computer = robot.computer + + // ----------------------------------------------------------------------- // + + // Note: we implement IRobotContext in the TE to allow external components + //to cast their owner to it (to allow interacting with their owning robot). + + override def isRunning = robot.isRunning + + override def isRunning_=(value: Boolean) = robot.isRunning_=(value) + + def selectedSlot() = robot.selectedSlot + + def player() = robot.player() + + def saveUpgrade() = robot.saveUpgrade() + // ----------------------------------------------------------------------- // @LuaCallback("start") @@ -221,12 +240,6 @@ class RobotProxy(val robot: Robot) extends Computer(robot.isClient) with ISidedI // ----------------------------------------------------------------------- // - override def computer = robot.computer - - override def isRunning = robot.isRunning - - override def isRunning_=(value: Boolean) = robot.isRunning_=(value) - override def markAsChanged() = robot.markAsChanged() override def hasRedstoneCard = robot.hasRedstoneCard diff --git a/li/cil/oc/server/component/Generator.scala b/li/cil/oc/server/component/Generator.scala index 8ce30fe63..ada50fe87 100644 --- a/li/cil/oc/server/component/Generator.scala +++ b/li/cil/oc/server/component/Generator.scala @@ -81,6 +81,7 @@ class Generator(val owner: MCTileEntity) extends ManagedComponent { if (remainingTicks <= 0 && inventory.isDefined) { val stack = inventory.get remainingTicks = TileEntityFurnace.getItemBurnTime(stack) + if (remainingTicks > 0) updateClient() stack.stackSize -= 1 if (stack.stackSize <= 0) { inventory = None @@ -88,10 +89,16 @@ class Generator(val owner: MCTileEntity) extends ManagedComponent { } if (remainingTicks > 0) { remainingTicks -= 1 + if (remainingTicks == 0) updateClient() node.changeBuffer(Settings.get.ratioBuildCraft * Settings.get.generatorEfficiency) } } + private def updateClient() = owner match { + case robot: RobotContext => robot.saveUpgrade() + case _ => + } + // ----------------------------------------------------------------------- // override def onDisconnect(node: Node) { @@ -127,10 +134,14 @@ class Generator(val owner: MCTileEntity) extends ManagedComponent { inventory match { case Some(stack) => nbt.setNewCompoundTag("inventory", stack.writeToNBT) - nbt.setInteger("remainingTicks", remainingTicks) case _ => nbt.removeTag("inventory") - nbt.removeTag("remainingTicks") + } + if (remainingTicks > 0) { + nbt.setInteger("remainingTicks", remainingTicks) + } + else { + nbt.removeTag("remainingTicks") } } } diff --git a/li/cil/oc/server/component/Robot.scala b/li/cil/oc/server/component/Robot.scala index 12e9e6b1e..234da2607 100644 --- a/li/cil/oc/server/component/Robot.scala +++ b/li/cil/oc/server/component/Robot.scala @@ -36,6 +36,8 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) with RobotConte def player = robot.player() + def saveUpgrade() = robot.saveUpgrade() + @LuaCallback(value = "level", direct = true) def level(context: Context, args: Arguments): Array[AnyRef] = { result(robot.level + robot.xp / robot.xpForNextLevel)