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

This commit is contained in:
Florian Nücke 2013-12-21 19:06:52 +01:00
parent 26b5d0543e
commit 5dae4cd20e
10 changed files with 122 additions and 30 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -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.
* <p/>
* If no upgrade is installed in the robot this does nothing.
* <p/>
* 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();
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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