From 5dae4cd20ed0475c0ac090eb1230873ee3fb59d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Sat, 21 Dec 2013 19:06:52 +0100 Subject: [PATCH] added callback to robot context that allows forcing an upgrade to be saved and resent to clients, to allow upgrades to manually synchronize their data with clients for state specific rendering; using this to transmit generator state (running or not) and show a different texture accordingly; fixed a bug where remaining runtime wasn't saved for generators when no more items were in their internal inventory; implementing contexts in their respective tile entities, too, to allow external components to refer to their owners via the context interfaces --- .../items/upgrade_generator_equipped.png | Bin 1688 -> 1673 bytes li/cil/oc/api/network/RobotContext.java | 13 ++++++ li/cil/oc/client/PacketHandler.scala | 2 +- .../renderer/item/UpgradeRenderer.scala | 16 +++++--- .../tileentity/ComponentInventory.scala | 8 ++-- li/cil/oc/common/tileentity/Computer.scala | 31 ++++++++++---- li/cil/oc/common/tileentity/Robot.scala | 38 +++++++++++++++--- li/cil/oc/common/tileentity/RobotProxy.scala | 27 +++++++++---- li/cil/oc/server/component/Generator.scala | 15 ++++++- li/cil/oc/server/component/Robot.scala | 2 + 10 files changed, 122 insertions(+), 30 deletions(-) diff --git a/assets/opencomputers/textures/items/upgrade_generator_equipped.png b/assets/opencomputers/textures/items/upgrade_generator_equipped.png index 7c810babf5606e611595c408331a2229b8a3e20b..abbf62d32d9c5da354f89d9b3229cfc550300882 100644 GIT binary patch delta 1619 zcmV-Z2CVs*4T%kqNq?zOOjJcjM@Ikv03;+NLPA1BL_{JYB0xYuMn*;>BO^*mN+l&F zK|w)7LqknXO+`gTTwGjtcXxk(e|&s=K0ZDsCMHx=RDghhXlQ6mOiWNvQ2zh`US3`y zAt6ywQG$YkeSLjAJUn%Ebv->jWMpJ0C@5}jZgg~XH#avmHGeg5aByK^VP$1yOG`^B zDJdr>Cv9zQczAeud3k$#dwza?OifLJfq__9Sl4_$KR-W6NJwmKY)(#2Q&UqdEiF4c zJO6M1RaI3rX=!O+UthyiD*tos zV`E}rVq#-r%4;^kRx2hZCm$al|5O0KXf$GCWB*(L@BjciIyz};YBMu4A|oRrBO*XQ zKS@eS{b~UIVE|rVU+P%^BO)Smb8p{bc}r zeSBJ5TRuKNVq;^@Yc=g=0R8{~^kM*FVPR-#Xn!;`G`vwLYHDg+TwQ5sY5#lx=KugT zG&D*{NoircVJnQ3Zg5Z<@VW{cPs-_z$ve4c6cgzfL@<+?#S9EmL&bzUdtV|kl=z{um`F3jN(3{fM=%@Vp>N&p(ckxEPl~zS zLq84vYFq&+a-4;563B^IY=1Pi`TW>LpKlu7bj#**^eikEko6oaKq1!7`u$eFb(X~M z|FpZnnh^*jm5{Impl9A$pI`0VT3=Y$|IEVv9v--rJVb-88R3QgoU`BCKkamOPkVzf z#v7UtbuVTa#6wV!JO_gZgWj_|K~I-5U0@>BnDW!>AZnr!E>RRkR7|9t0*XbEBnKnk z1XA}S(qIsfbQ&;pfFMa75+egacdr+_iGgR-RGw!{7robv4w{47O}qaE7y#;YsDSFE RV*CIA002ovPDHLkV1l@N+l&FO-)T>V`D)7cXxMxe}7+JUwnLgCMG6;fPhp~R6agFXlQ6m zOiWNvQ2zh`At51seSJ|;QG$Ykb#-+-JUl%;Jt!zBZfa49J%H#av+ zOG{l{T_-0eSXfwoetvm*d0JXpdwY9rZEb;pflN(J*L*&Bcz9=LXH!#CPEJm2Y;6B< z07ytkRaI3xJ3C29Ni8icM@L8hYXEX{b1Et-GBPqbIXQNAc57>EPft&8aBo^!SyEC` zF)=ZEdU|njaesq?gM)*E|7HLnARuI8WIsPYI5;>(MMYLtR{voD`~UzhE-v!`0Ayri z!&EA1X=yAhEOK&kT3K2qCM9!obN_SzIyyRUZ*OpKa93AXQBhF(002o!N-HWV|5yO( zSpYykKU`d0CMGA!Yc_*|f+Z#V0Z2)3nVf11EdwYDzZ8u3tO78#w z{9pioet%(LVZl}_e0+OGMn*e2I=)gVG&D5rW&m|`boOTe@pJ%XWMnTdFMNG`{B8iu zaX497Sz23L#8@on003)iYUf-4USD5ZTU$j&MSnFlG;?!u|5O0~TmW@;b%TR~f`Wr` za&uy0V@RgRul%^Y;`8}jzLjM=3?%}J zM;Y?Pl1gjxyw;QwQ)c4o^dm=hU4I%$3_+oWm?g23NX5P8- zm+eoC1;NP)sL%}Jt!{1zZ4nrd5~<{2B90hF^izU?7@x@d@Bc0S-L^j#-neOC+kNpol*1=m!@JYr za5}B0y&KBqUavRo^%jfiwBhykrhj4gz?t>XDSGJZ;;`|2G351zhV@XrK5QISKOCwn z44SO0sfpxjcCFR=D`QR9M9pnelvE%GEEXkY&RwBjft&ynqJm&$!Wz~vSHZe!M{`GW zb2ZjX)X`xVpiKr@)f^l|02_njVlG#EyUXQjbyZ!}52hFZ$RJ6;xs4J97=K#zLwjrc z(nYOv&R5%?tp%7=q7)G%w@R6943hizIGqXS^x5W`NL(&}C`lq{pn^mkh4tgtjqm)n z<@3MyTu~eUxuw83RS>zT$`aUAU{L6;WrE^AnM`-)#=cr6$7C=`Qr)z)saE*r=3r31 zRTbak!C?12aIduiQs!=ljel?w$Vtv+zV&5({sn90wO?*Ml@UOj4NB%4Y(OP8mmChw zp_ND-j!$~ZtWiPK0NhlBM`2{aUs-7LAFRyJ?>c9G*N6aa5M=_wC2#;(;poAle=s~a z>-YD}hU23Xf<)(K)oi0$0=@>ydgxI6P&|BkC?4uHX3LCIkLi8X2V0qc%&l;OVHmF& zdeIm)HW<3@Wg?ddq)az(krT+MZqo4Y1EY$r59o{m{|e0WAe$(yATS!LATVaCIcr8A g8cgG+-G2fM07WsXqGHWsJOBUy07*qoM6N<$f=$TD0RR91 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)