diff --git a/li/cil/oc/Settings.scala b/li/cil/oc/Settings.scala index e4f95d0e4..933755672 100644 --- a/li/cil/oc/Settings.scala +++ b/li/cil/oc/Settings.scala @@ -71,11 +71,11 @@ class Settings(config: Config) { val ratioBuildCraft = config.getDouble("power.ratioBuildCraft").toFloat val ratioIndustrialCraft2 = config.getDouble("power.ratioIndustrialCraft2").toFloat val ratioUniversalElectricity = config.getDouble("power.ratioUniversalElectricity").toFloat + val chargeRate = config.getDouble("power.chargerChargeRate") // power.buffer val bufferCapacitor = config.getDouble("power.buffer.capacitor") max 0 val bufferCapacitorAdjacencyBonus = config.getDouble("power.buffer.capacitorAdjacencyBonus") max 0 - val bufferChargingStation = 50000.0 val bufferRobot = config.getDouble("power.buffer.robot") max 0 // power.cost diff --git a/li/cil/oc/api/network/Connector.java b/li/cil/oc/api/network/Connector.java index b0449f6a2..7189915bd 100644 --- a/li/cil/oc/api/network/Connector.java +++ b/li/cil/oc/api/network/Connector.java @@ -53,24 +53,30 @@ public interface Connector extends Node { * a program tries to display text on it. For running costs just apply the * same delta each tick. *

- * For negative values, if there is not enough energy stored in the buffer - * this will return false, and the operation depending on that - * energy should fail - what energy there is will still be consumed, though! + * If the specified delta cannot be completely applied to the buffer, the + * remaining delta will be returned. This means that for negative values + * a part of the energy will have been consumed, though. *

- * For positive values, if there is a buffer overflow due to the added - * energy the surplus will be lost and this will return false. - *

- * If there is enough energy or no overflow this will return true. + * If there is enough energy or no overflow this will return 0. *

* Keep in mind that this change is applied to the global buffer, * i.e. energy from multiple buffers may be consumed / multiple buffers may * be filled. The buffer for which this method is called (i.e. this node * instance) will be prioritized, though. * - * @param delta the amount of energy to consume or make available. - * @return whether the energy could be consumed or stored. + * @param delta the amount of energy to consume or store. + * @return the remainder of the delta that could not be applied. */ - boolean changeBuffer(double delta); + double changeBuffer(double delta); + + /** + * Like {@link #changeBuffer}, but will only store/consume the specified + * amount of energy if there is enough capacity/energy available. + * + * @param delta the amount of energy to consume or store. + * @return true if the energy was successfully consumed or stored. + */ + boolean tryChangeBuffer(double delta); /** * Change the size of the connectors local buffer. diff --git a/li/cil/oc/common/block/Charger.scala b/li/cil/oc/common/block/Charger.scala new file mode 100644 index 000000000..5725bd6e9 --- /dev/null +++ b/li/cil/oc/common/block/Charger.scala @@ -0,0 +1,37 @@ +package li.cil.oc.common.block + +import java.util +import li.cil.oc.Settings +import li.cil.oc.common.tileentity +import li.cil.oc.util.Tooltip +import net.minecraft.client.renderer.texture.IconRegister +import net.minecraft.entity.player.EntityPlayer +import net.minecraft.util.Icon +import net.minecraft.world.World +import net.minecraftforge.common.ForgeDirection + +class Charger(val parent: SimpleDelegator) extends SimpleDelegate { + val unlocalizedName = "Charger" + + var icon: Icon = null + + override def addInformation(player: EntityPlayer, tooltip: util.List[String], advanced: Boolean) { + tooltip.addAll(Tooltip.get(unlocalizedName)) + } + + override def icon(side: ForgeDirection) = Some(icon) + + override def registerIcons(iconRegister: IconRegister) = { + icon = iconRegister.registerIcon(Settings.resourceDomain + ":charger") + } + + override def hasTileEntity = true + + override def createTileEntity(world: World) = Some(new tileentity.Charger()) + + override def onNeighborBlockChange(world: World, x: Int, y: Int, z: Int, blockId: Int) = + world.getBlockTileEntity(x, y, z) match { + case charger: tileentity.Charger => charger.onNeighborChanged() + case _ => + } +} diff --git a/li/cil/oc/common/tileentity/Charger.scala b/li/cil/oc/common/tileentity/Charger.scala new file mode 100644 index 000000000..f58563775 --- /dev/null +++ b/li/cil/oc/common/tileentity/Charger.scala @@ -0,0 +1,53 @@ +package li.cil.oc.common.tileentity + +import li.cil.oc.api.network.{Node, Visibility} +import li.cil.oc.client.{PacketSender => ClientPacketSender} +import li.cil.oc.{Settings, api} +import net.minecraftforge.common.ForgeDirection + +class Charger extends Environment with Redstone { + val node = api.Network.newNode(this, Visibility.None). + withConnector(). + create() + + private val robots = Array.fill(6)(None: Option[RobotProxy]) + + private var chargeSpeed = 0.0 + + override def updateEntity() { + super.updateEntity() + updateRedstoneInput() + + if (chargeSpeed > 0) { + val charge = Settings.get.chargeRate * chargeSpeed + robots.collect { + case Some(proxy) => node.changeBuffer(proxy.robot.battery.changeBuffer(charge + node.changeBuffer(-charge))) + } + } + } + + override def validate() { + super.validate() + if (isClient) { + ClientPacketSender.sendRedstoneStateRequest(this) + } + } + + override def onConnect(node: Node) { + super.onConnect(node) + if (node == this.node) { + onNeighborChanged() + } + } + + override protected def onRedstoneInputChanged(side: ForgeDirection) { + super.onRedstoneInputChanged(side) + chargeSpeed = 0.0 max (ForgeDirection.VALID_DIRECTIONS.map(input).max min 15) / 15.0 + } + + def onNeighborChanged() { + ForgeDirection.VALID_DIRECTIONS.map(side => (side.ordinal(), world.getBlockTileEntity(x + side.offsetX, y + side.offsetY, z + side.offsetZ))).collect { + case (side, proxy: RobotProxy) => robots(side) = Some(proxy) + } + } +} diff --git a/li/cil/oc/common/tileentity/Computer.scala b/li/cil/oc/common/tileentity/Computer.scala index 531498f74..ba4380df7 100644 --- a/li/cil/oc/common/tileentity/Computer.scala +++ b/li/cil/oc/common/tileentity/Computer.scala @@ -117,9 +117,7 @@ abstract class Computer(isRemote: Boolean) extends Environment with ComponentInv override protected def onRedstoneInputChanged(side: ForgeDirection) { super.onRedstoneInputChanged(side) - if (isServer) { - computer.signal("redstone_changed", Int.box(side.ordinal())) - } + computer.signal("redstone_changed", Int.box(side.ordinal())) } // ----------------------------------------------------------------------- // diff --git a/li/cil/oc/common/tileentity/Redstone.scala b/li/cil/oc/common/tileentity/Redstone.scala index 61480add0..db782ceeb 100644 --- a/li/cil/oc/common/tileentity/Redstone.scala +++ b/li/cil/oc/common/tileentity/Redstone.scala @@ -51,7 +51,7 @@ trait Redstone extends TileEntity with network.Environment with Rotatable with P } def checkRedstoneInputChanged() { - shouldUpdateInput = true + shouldUpdateInput = isServer } // ----------------------------------------------------------------------- // diff --git a/li/cil/oc/common/tileentity/Robot.scala b/li/cil/oc/common/tileentity/Robot.scala index c547b5fbe..bb35bb435 100644 --- a/li/cil/oc/common/tileentity/Robot.scala +++ b/li/cil/oc/common/tileentity/Robot.scala @@ -206,8 +206,8 @@ class Robot(isRemote: Boolean) extends Computer(isRemote) with ISidedInventory w if (isServer) { if (computer.isRunning && !computer.isPaused) { // TODO just for testing... until we have charging stations - distributor.changeBuffer(Settings.get.robotCost + 0.1) - distributor.changeBuffer(Settings.get.computerCost - Settings.get.robotCost) + battery.changeBuffer(Settings.get.robotCost + 0.1) + battery.changeBuffer(Settings.get.computerCost - Settings.get.robotCost) } distributor.update() gpu.update() @@ -286,7 +286,6 @@ class Robot(isRemote: Boolean) extends Computer(isRemote) with ISidedInventory w computer.node.connect(gpu.node) distributor.node.connect(battery) buffer.node.connect(keyboard.node) - distributor.changeBuffer(distributor.globalBufferSize / 2) // TODO for testing only } } diff --git a/li/cil/oc/common/tileentity/Screen.scala b/li/cil/oc/common/tileentity/Screen.scala index 9f93c0213..0fe1eaaa4 100644 --- a/li/cil/oc/common/tileentity/Screen.scala +++ b/li/cil/oc/common/tileentity/Screen.scala @@ -76,7 +76,8 @@ class Screen(var tier: Int) extends Buffer with SidedEnvironment with Rotatable litPixels = buffer.lines.foldLeft(0)((acc, line) => acc + line.count(_ != ' ')) } val hadPower = hasPower - hasPower = buffer.node.changeBuffer(-(Settings.get.screenCost + pixelCost * litPixels)) + val neededPower = Settings.get.screenCost + pixelCost * litPixels + hasPower = buffer.node.tryChangeBuffer(-neededPower) if (hasPower != hadPower) { ServerPacketSender.sendScreenPowerChange(this, hasPower) } diff --git a/li/cil/oc/server/component/Computer.scala b/li/cil/oc/server/component/Computer.scala index 740a750dc..8ce415af5 100644 --- a/li/cil/oc/server/component/Computer.scala +++ b/li/cil/oc/server/component/Computer.scala @@ -220,11 +220,11 @@ class Computer(val owner: tileentity.Computer) extends ManagedComponent with Con Computer.State.Stopping | Computer.State.Stopped => // No power consumption. case Computer.State.Sleeping if lastUpdate < sleepUntil && signals.isEmpty => - if (!node.changeBuffer(-Settings.get.computerCost * Settings.get.sleepCostFactor)) { + if (!node.tryChangeBuffer(-Settings.get.computerCost * Settings.get.sleepCostFactor)) { crash("not enough energy") } case _ => - if (!node.changeBuffer(-Settings.get.computerCost)) { + if (!node.tryChangeBuffer(-Settings.get.computerCost)) { crash("not enough energy") } }) @@ -494,6 +494,9 @@ class Computer(val owner: tileentity.Computer) extends ManagedComponent with Con override def save(nbt: NBTTagCompound): Unit = this.synchronized { assert(state.top != Computer.State.Running) // Lock on 'this' should guarantee this. + // Make sure we don't continue running until everything has saved. + pause(0.05) + super.save(nbt) // Make sure the component list is up-to-date. diff --git a/li/cil/oc/server/component/Filesystem.scala b/li/cil/oc/server/component/Filesystem.scala index 5380bfe54..c0f2c8fb6 100644 --- a/li/cil/oc/server/component/Filesystem.scala +++ b/li/cil/oc/server/component/Filesystem.scala @@ -144,7 +144,7 @@ class FileSystem(val fileSystem: api.fs.FileSystem, var label: Label) extends Ma Array.copy(buffer, 0, bytes, 0, read) bytes } - if (!node.changeBuffer(-Settings.get.hddReadCost * bytes.length)) { + if (!node.tryChangeBuffer(-Settings.get.hddReadCost * bytes.length)) { throw new IOException("not enough energy") } result(bytes) @@ -179,7 +179,7 @@ class FileSystem(val fileSystem: api.fs.FileSystem, var label: Label) extends Ma def write(context: Context, args: Arguments): Array[AnyRef] = { val handle = args.checkInteger(0) val value = args.checkByteArray(1) - if (!node.changeBuffer(-Settings.get.hddWriteCost * value.length)) { + if (!node.tryChangeBuffer(-Settings.get.hddWriteCost * value.length)) { throw new IOException("not enough energy") } checkOwner(context.address, handle) diff --git a/li/cil/oc/server/component/GraphicsCard.scala b/li/cil/oc/server/component/GraphicsCard.scala index 0ccd6de26..55f5ba0c5 100644 --- a/li/cil/oc/server/component/GraphicsCard.scala +++ b/li/cil/oc/server/component/GraphicsCard.scala @@ -182,12 +182,14 @@ abstract class GraphicsCard extends ManagedComponent { s.fill(x, y, w, h, value.charAt(0)) result(true) } - else result(false) + else { + result(false) + } }) else throw new Exception("invalid fill value") } - private def consumePower(n: Double, cost: Double) = node.changeBuffer(-n * cost) + private def consumePower(n: Double, cost: Double) = node.tryChangeBuffer(-n * cost) // ----------------------------------------------------------------------- // diff --git a/li/cil/oc/server/component/PowerDistributor.scala b/li/cil/oc/server/component/PowerDistributor.scala index 32c452cb5..e6609349f 100644 --- a/li/cil/oc/server/component/PowerDistributor.scala +++ b/li/cil/oc/server/component/PowerDistributor.scala @@ -36,52 +36,61 @@ class PowerDistributor(val owner: PowerInformation) extends ManagedComponent { // ----------------------------------------------------------------------- // - def canChangeBuffer(delta: Double) = { - Settings.get.ignorePower || globalBuffer + delta >= 0 - } - - def changeBuffer(delta: Double): Boolean = { - if (delta != 0) this.synchronized { - val oldBuffer = globalBuffer - globalBuffer = (globalBuffer + delta) max 0 min globalBufferSize - if (globalBuffer != oldBuffer) { - dirty = true - if (delta < 0) { - var remaining = -delta - for (connector <- buffers) { - connector.synchronized(if (connector.localBuffer > 0) { - connector.dirty = true - if (connector.localBuffer < remaining) { - remaining -= connector.localBuffer - connector.localBuffer = 0 - } - else { - connector.localBuffer -= remaining - return true - } - }) - } - } - else if (delta > 0) { - var remaining = delta - for (connector <- buffers) { - connector.synchronized(if (connector.localBuffer < connector.localBufferSize) { - connector.dirty = true - val space = connector.localBufferSize - connector.localBuffer - if (space < remaining) { - remaining -= space - connector.localBuffer = connector.localBufferSize - } - else { - connector.localBuffer += remaining - return true - } - }) - } - } + def changeBuffer(delta: Double): Double = { + if (delta == 0) { + return 0 + } + if (Settings.get.ignorePower) { + if (delta < 0) { + return 0 + } + else /* if (delta > 0) */ { + return delta + } + } + this.synchronized { + val oldBuffer = globalBuffer + globalBuffer = (globalBuffer + delta) max 0 min globalBufferSize + if (globalBuffer == oldBuffer) { + return delta + } + dirty = true + if (delta < 0) { + var remaining = -delta + for (connector <- buffers) { + if (connector.localBuffer > 0) { + connector.dirty = true + if (connector.localBuffer < remaining) { + remaining -= connector.localBuffer + connector.localBuffer = 0 + } + else { + connector.localBuffer -= remaining + return 0 + } + } + } + remaining + } + else /* if (delta > 0) */ { + var remaining = delta + for (connector <- buffers) { + if (connector.localBuffer < connector.localBufferSize) { + connector.dirty = true + val space = connector.localBufferSize - connector.localBuffer + if (space < remaining) { + remaining -= space + connector.localBuffer = connector.localBufferSize + } + else { + connector.localBuffer += remaining + return 0 + } + } + } + remaining } } - false } // ----------------------------------------------------------------------- // diff --git a/li/cil/oc/server/component/Robot.scala b/li/cil/oc/server/component/Robot.scala index c3dfbd59d..ee17de95f 100644 --- a/li/cil/oc/server/component/Robot.scala +++ b/li/cil/oc/server/component/Robot.scala @@ -384,15 +384,15 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) { result(false, what) } else { - if (!robot.distributor.canChangeBuffer(-Settings.get.robotMoveCost)) { + if (!robot.battery.tryChangeBuffer(-Settings.get.robotMoveCost)) { result(false, "not enough energy") } else if (robot.move(direction)) { context.pause(Settings.get.moveDelay) - robot.distributor.changeBuffer(-Settings.get.robotMoveCost) result(true) } else { + robot.battery.changeBuffer(Settings.get.robotMoveCost) result(false, "impossible move") } } @@ -401,7 +401,7 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) { @LuaCallback("turn") def turn(context: Context, args: Arguments): Array[AnyRef] = { val clockwise = args.checkBoolean(0) - if (robot.distributor.changeBuffer(-Settings.get.robotTurnCost)) { + if (robot.battery.tryChangeBuffer(-Settings.get.robotTurnCost)) { if (clockwise) robot.rotate(ForgeDirection.UP) else robot.rotate(ForgeDirection.DOWN) robot.animateTurn(clockwise, Settings.get.turnDelay) diff --git a/li/cil/oc/server/component/WirelessNetworkCard.scala b/li/cil/oc/server/component/WirelessNetworkCard.scala index 366664907..028115576 100644 --- a/li/cil/oc/server/component/WirelessNetworkCard.scala +++ b/li/cil/oc/server/component/WirelessNetworkCard.scala @@ -103,7 +103,7 @@ class WirelessNetworkCard(val owner: TileEntity) extends NetworkCard { private def checkPower() { val cost = Settings.get.wirelessCostPerRange if (cost > 0 && !Settings.get.ignorePower) { - if (node.globalBuffer < cost || !node.changeBuffer(-strength * cost)) { + if (!node.tryChangeBuffer(-strength * cost)) { throw new IOException("not enough energy") } } diff --git a/li/cil/oc/server/component/robot/Player.scala b/li/cil/oc/server/component/robot/Player.scala index 7a8058253..834f5374d 100644 --- a/li/cil/oc/server/component/robot/Player.scala +++ b/li/cil/oc/server/component/robot/Player.scala @@ -289,7 +289,7 @@ class Player(val robot: Robot) extends EntityPlayer(robot.world, Settings.get.na override def addExhaustion(amount: Float) { if (Settings.get.robotExhaustionCost > 0) { - robot.distributor.changeBuffer(-Settings.get.robotExhaustionCost * amount) + robot.battery.changeBuffer(-Settings.get.robotExhaustionCost * amount) } } diff --git a/li/cil/oc/server/network/Connector.scala b/li/cil/oc/server/network/Connector.scala index 335e6fe3a..74dece7d5 100644 --- a/li/cil/oc/server/network/Connector.scala +++ b/li/cil/oc/server/network/Connector.scala @@ -25,10 +25,21 @@ trait Connector extends Node with network.Connector with Persistable { // ----------------------------------------------------------------------- // - def changeBuffer(delta: Double) = if (delta != 0) { - val remaining = this.synchronized { + def changeBuffer(delta: Double): Double = { + if (delta == 0) { + return 0 + } + if (Settings.get.ignorePower) { + if (delta < 0) { + return 0 + } + else /* if (delta > 0) */ { + return delta + } + } + def change() = { val oldBuffer = localBuffer - localBuffer = localBuffer + delta + localBuffer += delta val remaining = if (localBuffer < 0) { val remaining = localBuffer localBuffer = 0 @@ -43,17 +54,53 @@ trait Connector extends Node with network.Connector with Persistable { dirty ||= (localBuffer != oldBuffer) remaining } - distributor.fold(remaining == 0)(_.changeBuffer(remaining)) || Settings.get.ignorePower - } else true + this.synchronized(distributor match { + case Some(d) => d.synchronized(d.changeBuffer(change())) + case _ => change() + }) + } + + def tryChangeBuffer(delta: Double): Boolean = { + if (delta == 0) { + return true + } + if (Settings.get.ignorePower) { + if (delta < 0) { + return true + } + else /* if (delta > 0) */ { + return false + } + } + this.synchronized(distributor match { + case Some(d) => d.synchronized { + val newGlobalBuffer = globalBuffer + delta + newGlobalBuffer >= 0 && newGlobalBuffer <= globalBufferSize && d.changeBuffer(delta) == 0 + } + case _ => + val newLocalBuffer = localBuffer + delta + if (newLocalBuffer < 0 || newLocalBuffer > localBufferSize) { + false + } + else { + localBuffer = newLocalBuffer + true + } + }) + } def setLocalBufferSize(size: Double) { - val remaining = this.synchronized { - localBufferSize = size max 0 - val surplus = (localBuffer - localBufferSize) max 0 - localBuffer = localBuffer min localBufferSize - surplus - } - distributor.foreach(_.changeBuffer(remaining)) + this.synchronized(distributor match { + case Some(d) => d.synchronized { + localBufferSize = size max 0 + val surplus = (localBuffer - localBufferSize) max 0 + localBuffer = localBuffer min localBufferSize + d.changeBuffer(surplus) + } + case _ => + localBufferSize = size max 0 + localBuffer = localBuffer min localBufferSize + }) } // ----------------------------------------------------------------------- // @@ -64,8 +111,7 @@ trait Connector extends Node with network.Connector with Persistable { } else if (distributor.isEmpty) { node.host match { - case distributor: PowerDistributor => - this.distributor = Some(distributor) + case d: PowerDistributor => this.synchronized(distributor = Some(d)) case _ => } } @@ -73,7 +119,7 @@ trait Connector extends Node with network.Connector with Persistable { } override def onDisconnect(node: ImmutableNode) { - if (node == this) { + if (node == this) this.synchronized { setLocalBufferSize(0) distributor = None } @@ -84,7 +130,7 @@ trait Connector extends Node with network.Connector with Persistable { } private def findDistributor() = { - distributor = reachableNodes.find(_.host.isInstanceOf[PowerDistributor]).fold(None: Option[PowerDistributor])(n => Some(n.host.asInstanceOf[PowerDistributor])) + this.synchronized(distributor = reachableNodes.find(_.host.isInstanceOf[PowerDistributor]).fold(None: Option[PowerDistributor])(n => Some(n.host.asInstanceOf[PowerDistributor]))) } // ----------------------------------------------------------------------- // diff --git a/reference.conf b/reference.conf index be83aaaeb..7f11a4c34 100644 --- a/reference.conf +++ b/reference.conf @@ -240,6 +240,13 @@ opencomputers { # internal energy units one Joule generates. ratioUniversalElectricity: 5.0 + # The amount of energy a Charge transfers to each adjacent robot per tick + # if a maximum strength redstone signal is set. Chargers load robots with + # a controllable speed, based on the maximum strength of redstone signals + # going into the block. So if a redstone signal of eight is set, it'll + # charge robots at roughly half speed. + chargerChargeRate: 2500.0 + buffer { # The amount of energy a single capacitor can store. capacitor: 8000.0