blob: bbdcd1caa871db5e6b6cb9572c4a7d8bfb70880f [file] [log] [blame] [raw]
package li.cil.oc.common.tileentity
import java.util
import com.google.common.base.Strings
import cpw.mods.fml.common.Optional.Method
import cpw.mods.fml.common.eventhandler.SubscribeEvent
import cpw.mods.fml.relauncher.Side
import cpw.mods.fml.relauncher.SideOnly
import li.cil.oc._
import li.cil.oc.api.Network
import li.cil.oc.api.internal
import li.cil.oc.api.network.Analyzable
import li.cil.oc.api.network._
import li.cil.oc.client.Sound
import li.cil.oc.common.Tier
import li.cil.oc.integration.Mods
import li.cil.oc.integration.opencomputers.DriverRedstoneCard
import li.cil.oc.integration.stargatetech2.DriverAbstractBusCard
import li.cil.oc.integration.util.Waila
import li.cil.oc.server.component
import li.cil.oc.server.{PacketSender => ServerPacketSender}
import li.cil.oc.util.ExtendedNBT._
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.item.ItemStack
import net.minecraft.nbt.NBTTagCompound
import net.minecraft.nbt.NBTTagString
import net.minecraftforge.common.util.Constants.NBT
import net.minecraftforge.common.util.ForgeDirection
import net.minecraftforge.event.world.WorldEvent
import scala.collection.mutable
class ServerRack extends traits.PowerAcceptor with traits.Hub with traits.PowerBalancer with traits.Inventory with traits.Rotatable with traits.BundledRedstoneAware with traits.AbstractBusAware with Analyzable with internal.ServerRack with traits.StateAware {
val servers = Array.fill(getSizeInventory)(None: Option[component.Server])
val sides = Seq(Option(ForgeDirection.UP), Option(ForgeDirection.EAST), Option(ForgeDirection.WEST), Option(ForgeDirection.DOWN)).
padTo(servers.length, None).toArray
val terminals = servers.indices.map(new common.component.Terminal(this, _)).toArray
var range = 16
// For client side, where we don't create the component.
private val _isRunning = new Array[Boolean](getSizeInventory)
private val _hasErrored = new Array[Boolean](getSizeInventory)
private var markChunkDirty = false
var internalSwitch = false
// For client side rendering.
var isPresent = Array.fill[Option[String]](getSizeInventory)(None)
// Used on client side to check whether to render disk activity indicators.
var lastAccess = Array.fill(4)(0L)
val builtInSwitchTier = Settings.get.serverRackSwitchTier
relayDelay = math.max(1, relayBaseDelay - (builtInSwitchTier + 1) * relayDelayPerUpgrade)
relayAmount = math.max(1, relayBaseAmount + (builtInSwitchTier + 1) * relayAmountPerUpgrade)
maxQueueSize = math.max(1, queueBaseSize + (builtInSwitchTier + 1) * queueSizePerUpgrade)
override def server(slot: Int) = servers(slot).orNull
@SideOnly(Side.CLIENT)
override protected def hasConnector(side: ForgeDirection) = side != facing
override protected def connector(side: ForgeDirection) = Option(if (side != facing) sidedNode(side).asInstanceOf[Connector] else null)
override def energyThroughput = Settings.get.serverRackRate
override def getWorld = world
// ----------------------------------------------------------------------- //
override def canConnect(side: ForgeDirection) = side != facing
override def sidedNode(side: ForgeDirection): Node = if (side != facing) super.sidedNode(side) else null
@Method(modid = Mods.IDs.StargateTech2)
override def getInterfaces(side: Int) = if (side != facing.ordinal) {
super.getInterfaces(side)
}
else null
// ----------------------------------------------------------------------- //
def isRunning(number: Int) =
if (isServer) servers(number).fold(false)(_.machine.isRunning)
else _isRunning(number)
@SideOnly(Side.CLIENT)
def setRunning(number: Int, value: Boolean): Unit = {
_isRunning(number) = value
if (!value) {
_hasErrored(number) = false
}
world.markBlockForUpdate(x, y, z)
if (anyRunning) Sound.startLoop(this, "computer_running", 1.5f, 50 + world.rand.nextInt(50))
else Sound.stopLoop(this)
}
def anyRunning = servers.indices.exists(isRunning)
def hasErrored(number: Int) =
if (isServer) servers(number).fold(false)(_.machine.lastError != null)
else _hasErrored(number)
@SideOnly(Side.CLIENT)
def setErrored(number: Int, value: Boolean): Unit = {
_hasErrored(number) = value
}
def anyErrored = servers.indices.exists(hasErrored)
override def currentState = {
if (anyRunning) util.EnumSet.of(traits.State.IsWorking)
else util.EnumSet.noneOf(classOf[traits.State])
}
// ----------------------------------------------------------------------- //
def markForSaving() = markChunkDirty = true
override def installedComponents = servers.flatMap {
case Some(server) => server.inventory.components collect {
case Some(component) => component
}
case _ => Iterable.empty
}
def hasAbstractBusCard = servers exists {
case Some(server) => server.machine.isRunning && server.inventory.items.exists {
case Some(stack) => DriverAbstractBusCard.worksWith(stack, server.getClass)
case _ => false
}
case _ => false
}
def hasRedstoneCard = servers exists {
case Some(server) => server.machine.isRunning && server.inventory.items.exists {
case Some(stack) => DriverRedstoneCard.worksWith(stack, server.getClass)
case _ => false
}
case _ => false
}
def reconnectServer(number: Int, server: component.Server) {
sides(number) match {
case Some(serverSide) =>
val serverNode = server.machine.node
for (side <- ForgeDirection.VALID_DIRECTIONS if side != facing) {
if (toLocal(side) == serverSide) sidedNode(side).connect(serverNode)
else sidedNode(side).disconnect(serverNode)
}
case _ =>
}
}
// ----------------------------------------------------------------------- //
override protected def distribute() = {
def node(side: Int) = sides(side) match {
case None | Some(ForgeDirection.UNKNOWN) => servers(side).fold(null: Connector)(_.machine.node.asInstanceOf[Connector])
case _ => null
}
val nodes = (0 to 3).map(node)
def network(connector: Connector) = if (connector != null && connector.network != null) connector.network else this
val (sumBuffer, sumSize) = super.distribute()
var sumBufferServers, sumSizeServers = 0.0
network(nodes(0)).synchronized {
network(nodes(1)).synchronized {
network(nodes(2)).synchronized {
network(nodes(3)).synchronized {
for (node <- nodes if node != null) {
sumBufferServers += node.globalBuffer
sumSizeServers += node.globalBufferSize
}
if (sumSize + sumSizeServers > 0) {
val ratio = (sumBuffer + sumBufferServers) / (sumSize + sumSizeServers)
for (node <- nodes if node != null) {
node.changeBuffer(node.globalBufferSize * ratio - node.globalBuffer)
}
}
}
}
}
}
(sumBuffer + sumBufferServers, sumSize + sumSizeServers)
}
// ----------------------------------------------------------------------- //
override protected def relayPacket(sourceSide: Option[ForgeDirection], packet: Packet) {
if (internalSwitch) {
for (slot <- servers.indices) {
val side = sides(slot).map(toGlobal)
if (side != sourceSide) {
servers(slot) match {
case Some(server) => server.machine.node.sendToNeighbors("network.message", packet)
case _ =>
}
}
}
}
else super.relayPacket(sourceSide, packet)
}
override protected def onPlugMessage(plug: Plug, message: Message) {
// This check is a little hacky. Basically what we test here is whether
// the message was relayed internally, because only internally relayed
// network messages originate from the actual server nodes themselves.
// The otherwise come from the network card.
if (message.name != "network.message" || !(servers collect {
case Some(server) => server.machine.node
}).contains(message.source)) super.onPlugMessage(plug, message)
}
// ----------------------------------------------------------------------- //
override def getSizeInventory = 4
override def getInventoryStackLimit = 1
override def isItemValidForSlot(i: Int, stack: ItemStack) = {
val descriptor = api.Items.get(stack)
descriptor == api.Items.get(Constants.ItemName.ServerTier1) ||
descriptor == api.Items.get(Constants.ItemName.ServerTier2) ||
descriptor == api.Items.get(Constants.ItemName.ServerTier3) ||
descriptor == api.Items.get(Constants.ItemName.ServerCreative)
}
// ----------------------------------------------------------------------- //
override def onAnalyze(player: EntityPlayer, side: Int, hitX: Float, hitY: Float, hitZ: Float) = {
slotAt(ForgeDirection.getOrientation(side), hitX, hitY, hitZ) match {
case Some(slot) => servers(slot) match {
case Some(server) => Array(server.machine.node)
case _ => null
}
case _ => Array(sidedNode(ForgeDirection.getOrientation(side)))
}
}
def slotAt(side: ForgeDirection, hitX: Float, hitY: Float, hitZ: Float) = {
if (side == facing) {
val l = 2 / 16.0
val h = 14 / 16.0
val slot = (((1 - hitY) - l) / (h - l) * 4).toInt
Some(math.max(0, math.min(servers.length - 1, slot)))
}
else None
}
// ----------------------------------------------------------------------- //
override def canUpdate = isServer
override def updateEntity() {
super.updateEntity()
if (isServer && isConnected) {
val shouldUpdatePower = world.getTotalWorldTime % Settings.get.tickFrequency == 0
if (shouldUpdatePower && range > 0 && !Settings.get.ignorePower) {
val countRunning = servers.count {
case Some(server) => server.machine.isRunning
case _ => false
}
if (countRunning > 0) {
var cost = -(countRunning * range * Settings.get.wirelessCostPerRange * Settings.get.tickFrequency)
for (side <- ForgeDirection.VALID_DIRECTIONS if cost < 0) {
sidedNode(side) match {
case connector: Connector => cost = connector.changeBuffer(cost)
case _ =>
}
}
}
}
servers collect {
case Some(server) =>
if (shouldUpdatePower && server.tier == Tier.Four) {
server.machine.node.asInstanceOf[Connector].changeBuffer(Double.PositiveInfinity)
}
server.machine.update()
}
if (markChunkDirty) {
markChunkDirty = false
world.markTileEntityChunkModified(x, y, z, this)
}
for (i <- servers.indices) {
val isRunning = servers(i).fold(false)(_.machine.isRunning)
val errored = servers(i).fold(false)(_.machine.lastError != null)
if (_isRunning(i) != isRunning || _hasErrored(i) != errored) {
_isRunning(i) = isRunning
_hasErrored(i) = errored
markDirty()
ServerPacketSender.sendServerState(this, i)
world.notifyBlocksOfNeighborChange(x, y, z, block)
}
}
isOutputEnabled = hasRedstoneCard
isAbstractBusAvailable = hasAbstractBusCard
servers collect {
case Some(server) =>
server.inventory.updateComponents()
terminals(server.slot).buffer.update()
}
}
}
// ----------------------------------------------------------------------- //
override protected def initialize() {
super.initialize()
if (isClient) {
ServerRack.list += this -> Unit
}
}
override def dispose() {
super.dispose()
if (isClient) {
ServerRack.list -= this
}
else {
servers collect {
case Some(server) => server.machine.stop()
}
}
}
override def readFromNBTForServer(nbt: NBTTagCompound) {
super.readFromNBTForServer(nbt)
for (slot <- 0 until getSizeInventory) {
if (getStackInSlot(slot) != null) {
val server = new component.Server(this, slot)
servers(slot) = Option(server)
}
}
nbt.getTagList(Settings.namespace + "servers", NBT.TAG_COMPOUND).toArray[NBTTagCompound].
zipWithIndex.foreach {
case (tag, index) if index < servers.length =>
servers(index) match {
case Some(server) =>
try server.load(tag) catch {
case t: Throwable => OpenComputers.log.warn("Failed restoring server state. Please report this!", t)
}
case _ =>
}
case _ =>
}
val sidesNbt = nbt.getByteArray(Settings.namespace + "sides").map {
case side if side >= 0 => Option(ForgeDirection.getOrientation(side))
case _ => None
}
Array.copy(sidesNbt, 0, sides, 0, math.min(sidesNbt.length, sides.length))
nbt.getTagList(Settings.namespace + "terminals", NBT.TAG_COMPOUND).toArray[NBTTagCompound].
zipWithIndex.foreach {
case (tag, index) if index < terminals.length =>
try terminals(index).load(tag) catch {
case t: Throwable => OpenComputers.log.warn("Failed restoring terminal state. Please report this!", t)
}
case _ =>
}
range = nbt.getInteger(Settings.namespace + "range")
internalSwitch = nbt.getBoolean(Settings.namespace + "internalSwitch")
// Kickstart initialization to avoid values getting overwritten by
// readFromNBTForClient if that packet is handled after a manual
// initialization / state change packet.
for (i <- servers.indices) {
val isRunning = servers(i).fold(false)(_.machine.isRunning)
_isRunning(i) = isRunning
}
_isOutputEnabled = hasRedstoneCard
_isAbstractBusAvailable = hasAbstractBusCard
}
// Side check for Waila (and other mods that may call this client side).
override def writeToNBTForServer(nbt: NBTTagCompound) = if (isServer) {
if (!Waila.isSavingForTooltip) {
nbt.setNewTagList(Settings.namespace + "servers", servers map {
case Some(server) =>
val serverNbt = new NBTTagCompound()
try server.save(serverNbt) catch {
case t: Throwable => OpenComputers.log.warn("Failed saving server state. Please report this!", t)
}
serverNbt
case _ => new NBTTagCompound()
})
}
super.writeToNBTForServer(nbt)
nbt.setByteArray(Settings.namespace + "sides", sides.map {
case Some(side) => side.ordinal.toByte
case _ => -1: Byte
})
nbt.setNewTagList(Settings.namespace + "terminals", terminals.map(t => {
val terminalNbt = new NBTTagCompound()
try t.save(terminalNbt) catch {
case t: Throwable => OpenComputers.log.warn("Failed saving terminal state. Please report this!", t)
}
terminalNbt
}))
nbt.setInteger(Settings.namespace + "range", range)
nbt.setBoolean(Settings.namespace + "internalSwitch", internalSwitch)
}
@SideOnly(Side.CLIENT)
override def readFromNBTForClient(nbt: NBTTagCompound) {
super.readFromNBTForClient(nbt)
val isRunningNbt = nbt.getBooleanArray("isServerRunning")
Array.copy(isRunningNbt, 0, _isRunning, 0, math.min(isRunningNbt.length, _isRunning.length))
val hasErroredNbt = nbt.getBooleanArray("hasServerErrored")
Array.copy(hasErroredNbt, 0, _hasErrored, 0, math.min(hasErroredNbt.length, _hasErrored.length))
val isPresentNbt = nbt.getTagList("isPresent", NBT.TAG_STRING).map((tag: NBTTagString) => {
val value = tag.func_150285_a_()
if (Strings.isNullOrEmpty(value)) None else Some(value)
}).toArray
Array.copy(isPresentNbt, 0, isPresent, 0, math.min(isPresentNbt.length, isPresent.length))
val sidesNbt = nbt.getByteArray("sides").map {
case side if side >= 0 => Option(ForgeDirection.getOrientation(side))
case _ => None
}
Array.copy(sidesNbt, 0, sides, 0, math.min(sidesNbt.length, sides.length))
nbt.getTagList("terminals", NBT.TAG_COMPOUND).toArray[NBTTagCompound].
zipWithIndex.foreach {
case (tag, index) if index < terminals.length => terminals(index).readFromNBTForClient(tag)
case _ =>
}
range = nbt.getInteger("range")
if (anyRunning) Sound.startLoop(this, "computer_running", 1.5f, 1000 + world.rand.nextInt(2000))
}
override def writeToNBTForClient(nbt: NBTTagCompound) {
super.writeToNBTForClient(nbt)
nbt.setBooleanArray("isServerRunning", _isRunning)
nbt.setBooleanArray("hasServerErrored", _hasErrored)
nbt.setNewTagList("isPresent", servers.map(value => new NBTTagString(value.fold("")(_.machine.node.address))))
nbt.setByteArray("sides", sides.map {
case Some(side) => side.ordinal.toByte
case _ => -1: Byte
})
nbt.setNewTagList("terminals", terminals.map(t => {
val terminalNbt = new NBTTagCompound()
t.writeToNBTForClient(terminalNbt)
terminalNbt
}))
nbt.setInteger("range", range)
}
// ----------------------------------------------------------------------- //
override protected def onPlugConnect(plug: Plug, node: Node) {
if (node == plug.node) {
for (number <- servers.indices) {
val serverSide = sides(number).map(toGlobal)
servers(number) match {
case Some(server) =>
if (serverSide == Option(plug.side)) plug.node.connect(server.machine.node)
else api.Network.joinNewNetwork(server.machine.node)
terminals(number).connect(server.machine.node)
case _ =>
}
}
}
}
override protected def createNode(plug: Plug) = api.Network.newNode(plug, Visibility.Network).
withConnector(Settings.get.bufferDistributor).
create()
override protected def onItemAdded(slot: Int, stack: ItemStack) {
super.onItemAdded(slot, stack)
if (isServer) {
val server = new component.Server(this, slot)
servers(slot) = Some(server)
reconnectServer(slot, server)
Network.joinNewNetwork(server.machine.node)
terminals(slot).connect(server.machine.node)
}
}
override protected def onItemRemoved(slot: Int, stack: ItemStack) {
super.onItemRemoved(slot, stack)
if (isServer) {
servers(slot) match {
case Some(server) =>
server.machine.node.remove()
server.inventory.containerOverride = stack
server.inventory.save(new NBTTagCompound()) // Only flush components.
server.inventory.markDirty()
case _ =>
}
servers(slot) = None
terminals(slot).keys.clear()
}
}
override def markDirty() {
super.markDirty()
if (isServer) {
isOutputEnabled = hasRedstoneCard
isAbstractBusAvailable = hasAbstractBusCard
ServerPacketSender.sendServerPresence(this)
}
else {
world.markBlockForUpdate(x, y, z)
}
}
override protected def onRotationChanged() {
super.onRotationChanged()
checkRedstoneInputChanged()
}
override protected def onRedstoneInputChanged(side: ForgeDirection, oldMaxValue: Int, newMaxValue: Int) {
super.onRedstoneInputChanged(side, oldMaxValue, newMaxValue)
servers collect {
case Some(server) => server.machine.node.sendToNeighbors("redstone.changed", toLocal(side), Int.box(oldMaxValue), Int.box(newMaxValue))
}
}
override def rotate(axis: ForgeDirection) = false
}
object ServerRack {
val list = mutable.WeakHashMap.empty[ServerRack, Unit]
@SubscribeEvent
def onWorldUnload(e: WorldEvent.Unload) {
if (e.world.isRemote) {
list.clear()
}
}
}