diff --git a/src/main/java/li/cil/oc/api/event/NetworkActivityEvent.java b/src/main/java/li/cil/oc/api/event/NetworkActivityEvent.java new file mode 100644 index 000000000..e6ce5cfe9 --- /dev/null +++ b/src/main/java/li/cil/oc/api/event/NetworkActivityEvent.java @@ -0,0 +1,158 @@ +package li.cil.oc.api.event; + +import cpw.mods.fml.common.eventhandler.Event; +import li.cil.oc.api.network.Node; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.world.World; + +/** + * Events for handling network activity and representing it on the client. + *

+ * This is used to render network activity + * indicators on some containers (e.g. computer, server). + *

+ * Use this to implement rendering of disk access indicators on you own + * containers / computers / drive bays. + *

+ * Canceling this event is provided to allow registering higher priority + * event handlers that override default behavior. + */ +public class NetworkActivityEvent extends Event { + protected World world; + + protected double x; + + protected double y; + + protected double z; + + protected TileEntity tileEntity; + + protected NBTTagCompound data; + + /** + * Constructor for tile entity hosted network cards. + * + * @param tileEntity the tile entity hosting the network card. + * @param data the additional data. + */ + protected NetworkActivityEvent(TileEntity tileEntity, NBTTagCompound data) { + this.world = tileEntity.getWorldObj(); + this.x = tileEntity.xCoord + 0.5; + this.y = tileEntity.yCoord + 0.5; + this.z = tileEntity.zCoord + 0.5; + this.tileEntity = tileEntity; + this.data = data; + } + + /** + * Constructor for arbitrarily hosted network cards. + * + * @param world the world the network card lives in. + * @param x the x coordinate of the network card's container. + * @param y the y coordinate of the network card's container. + * @param z the z coordinate of the network card's container. + * @param data the additional data. + */ + protected NetworkActivityEvent(World world, double x, double y, double z, NBTTagCompound data) { + this.world = world; + this.x = x; + this.y = y; + this.z = z; + this.tileEntity = null; + this.data = data; + } + + /** + * The world the network card lives in. + */ + public World getWorld() { + return world; + } + + /** + * The x coordinate of the network card's container. + */ + public double getX() { + return x; + } + + /** + * The y coordinate of the network card's container. + */ + public double getY() { + return y; + } + + /** + * The z coordinate of the network card's container. + */ + public double getZ() { + return z; + } + + /** + * The tile entity hosting the network card. + *

+ * Important: this can be null, which is usually the + * case when the container is an entity or item. + */ + public TileEntity getTileEntity() { + return tileEntity; + } + + /** + * Addition custom data, this is used to transmit the number of the server + * in a server rack the network card lives in, for example. + */ + public NBTTagCompound getData() { + return data; + } + + public static final class Server extends NetworkActivityEvent { + private Node node; + + public Server(TileEntity tileEntity, Node node) { + super(tileEntity, new NBTTagCompound()); + this.node = node; + } + + public Server(World world, double x, double y, double z, Node node) { + super(world, x, y, z, new NBTTagCompound()); + this.node = node; + } + + /** + * The node of the network card that signalled activity. + */ + public Node getNode() { + return node; + } + } + + public static final class Client extends NetworkActivityEvent { + /** + * Constructor for tile entity hosted network card. + * + * @param tileEntity the tile entity hosting the network card. + * @param data the additional data. + */ + public Client(TileEntity tileEntity, NBTTagCompound data) { + super(tileEntity, data); + } + + /** + * Constructor for arbitrarily hosted network card. + * + * @param world the world the file system lives in. + * @param x the x coordinate of the network card's container. + * @param y the y coordinate of the network card's container. + * @param z the z coordinate of the network card's container. + * @param data the additional data. + */ + public Client(World world, double x, double y, double z, NBTTagCompound data) { + super(world, x, y, z, data); + } + } +} diff --git a/src/main/resources/assets/opencomputers/textures/blocks/ServerFrontNetworkActivity.png b/src/main/resources/assets/opencomputers/textures/blocks/ServerFrontNetworkActivity.png new file mode 100644 index 000000000..10351f509 Binary files /dev/null and b/src/main/resources/assets/opencomputers/textures/blocks/ServerFrontNetworkActivity.png differ diff --git a/src/main/scala/li/cil/oc/client/PacketHandler.scala b/src/main/scala/li/cil/oc/client/PacketHandler.scala index acf09a786..95a54c035 100644 --- a/src/main/scala/li/cil/oc/client/PacketHandler.scala +++ b/src/main/scala/li/cil/oc/client/PacketHandler.scala @@ -7,7 +7,7 @@ import cpw.mods.fml.common.network.FMLNetworkEvent.ClientCustomPacketEvent import li.cil.oc.Localization import li.cil.oc.Settings import li.cil.oc.api -import li.cil.oc.api.event.FileSystemAccessEvent +import li.cil.oc.api.event.{NetworkActivityEvent, FileSystemAccessEvent} import li.cil.oc.client.renderer.PetRenderer import li.cil.oc.common.Loot import li.cil.oc.common.PacketType @@ -63,6 +63,7 @@ object PacketHandler extends CommonPacketHandler { case PacketType.NanomachinesInputs => onNanomachinesInputs(p) case PacketType.NanomachinesPower => onNanomachinesPower(p) case PacketType.NetSplitterState => onNetSplitterState(p) + case PacketType.NetworkActivity => onNetworkActivity(p) case PacketType.ParticleEffect => onParticleEffect(p) case PacketType.PetVisibility => onPetVisibility(p) case PacketType.PowerState => onPowerState(p) @@ -173,6 +174,23 @@ object PacketHandler extends CommonPacketHandler { } } + def onNetworkActivity(p: PacketParser) = { + val data = CompressedStreamTools.read(p) + if (p.readBoolean()) p.readTileEntity[net.minecraft.tileentity.TileEntity]() match { + case Some(t) => + MinecraftForge.EVENT_BUS.post(new NetworkActivityEvent.Client(t, data)) + case _ => // Invalid packet. + } + else world(p.player, p.readInt()) match { + case Some(world) => + val x = p.readDouble() + val y = p.readDouble() + val z = p.readDouble() + MinecraftForge.EVENT_BUS.post(new NetworkActivityEvent.Client(world, x, y, z, data)) + case _ => // Invalid packet. + } + } + def onFloppyChange(p: PacketParser) = p.readTileEntity[DiskDrive]() match { case Some(t) => t.setInventorySlotContents(0, p.readItemStack()) diff --git a/src/main/scala/li/cil/oc/client/Textures.scala b/src/main/scala/li/cil/oc/client/Textures.scala index 20f871315..fdfffebb1 100644 --- a/src/main/scala/li/cil/oc/client/Textures.scala +++ b/src/main/scala/li/cil/oc/client/Textures.scala @@ -63,6 +63,7 @@ object Textures { val blockRackServerOn = new ResourceLocation(Settings.resourceDomain, "textures/blocks/ServerFrontOn.png") val blockRackServerError = new ResourceLocation(Settings.resourceDomain, "textures/blocks/ServerFrontError.png") val blockRackServerActivity = new ResourceLocation(Settings.resourceDomain, "textures/blocks/ServerFrontActivity.png") + val blockRackServerNetworkActivity = new ResourceLocation(Settings.resourceDomain, "textures/blocks/ServerFrontNetworkActivity.png") val blockRackTerminalServerOn = new ResourceLocation(Settings.resourceDomain, "textures/blocks/TerminalServerFrontOn.png") val blockRackTerminalServerPresence = new ResourceLocation(Settings.resourceDomain, "textures/blocks/TerminalServerFrontPresence.png") diff --git a/src/main/scala/li/cil/oc/client/renderer/tileentity/CaseRenderer.scala b/src/main/scala/li/cil/oc/client/renderer/tileentity/CaseRenderer.scala index b1fc8ba57..e578b083f 100644 --- a/src/main/scala/li/cil/oc/client/renderer/tileentity/CaseRenderer.scala +++ b/src/main/scala/li/cil/oc/client/renderer/tileentity/CaseRenderer.scala @@ -37,7 +37,7 @@ object CaseRenderer extends TileEntitySpecialRenderer { if (computer.isRunning) { renderFrontOverlay(Textures.blockCaseFrontOn) - if (System.currentTimeMillis() - computer.lastAccess < 400 && computer.world.rand.nextDouble() > 0.1) { + if (System.currentTimeMillis() - computer.lastFileSystemAccess < 400 && computer.world.rand.nextDouble() > 0.1) { renderFrontOverlay(Textures.blockCaseFrontActivity) } } diff --git a/src/main/scala/li/cil/oc/common/PacketType.scala b/src/main/scala/li/cil/oc/common/PacketType.scala index dd1f94ab8..3e03c09a6 100644 --- a/src/main/scala/li/cil/oc/common/PacketType.scala +++ b/src/main/scala/li/cil/oc/common/PacketType.scala @@ -27,6 +27,7 @@ object PacketType extends Enumeration { NanomachinesInputs, NanomachinesPower, NetSplitterState, + NetworkActivity, ParticleEffect, PetVisibility, // Goes both ways. PowerState, diff --git a/src/main/scala/li/cil/oc/common/event/FileSystemAccessHandler.scala b/src/main/scala/li/cil/oc/common/event/FileSystemAccessHandler.scala index c065ba41e..626bad367 100644 --- a/src/main/scala/li/cil/oc/common/event/FileSystemAccessHandler.scala +++ b/src/main/scala/li/cil/oc/common/event/FileSystemAccessHandler.scala @@ -20,7 +20,7 @@ object FileSystemAccessHandler { case server: Server => val containsNode = server.componentSlot(e.getNode.address) >= 0 if (containsNode) { - server.lastAccess = System.currentTimeMillis() + server.lastFileSystemAccess = System.currentTimeMillis() t.markChanged(slot) } case diskDrive: DiskDriveMountable => @@ -42,7 +42,7 @@ object FileSystemAccessHandler { e.getWorld.playSound(e.getX, e.getY, e.getZ, e.getSound, volume, 1, false) e.getTileEntity match { case t: DiskDrive => t.lastAccess = System.currentTimeMillis() - case t: Case => t.lastAccess = System.currentTimeMillis() + case t: Case => t.lastFileSystemAccess = System.currentTimeMillis() case t: Raid => t.lastAccess = System.currentTimeMillis() case _ => } diff --git a/src/main/scala/li/cil/oc/common/event/NetworkActivityHandler.scala b/src/main/scala/li/cil/oc/common/event/NetworkActivityHandler.scala new file mode 100644 index 000000000..c0d76fc60 --- /dev/null +++ b/src/main/scala/li/cil/oc/common/event/NetworkActivityHandler.scala @@ -0,0 +1,36 @@ +package li.cil.oc.common.event + +import cpw.mods.fml.common.eventhandler.SubscribeEvent +import li.cil.oc.api.event.NetworkActivityEvent +import li.cil.oc.api.internal.Rack +import li.cil.oc.common.tileentity.Case +import li.cil.oc.server.component.Server + +object NetworkActivityHandler { + @SubscribeEvent + def onNetworkActivity(e: NetworkActivityEvent.Server) { + e.getTileEntity match { + case t: Rack => + for (slot <- 0 until t.getSizeInventory) { + t.getMountable(slot) match { + case server: Server => + val containsNode = server.componentSlot(e.getNode.address) >= 0 + if (containsNode) { + server.lastNetworkActivity = System.currentTimeMillis() + t.markChanged(slot) + } + case _ => + } + } + case _ => + } + } + + @SubscribeEvent + def onNetworkActivity(e: NetworkActivityEvent.Client) { + e.getTileEntity match { + case t: Case => t.lastNetworkActivity = System.currentTimeMillis(); + case _ => + } + } +} diff --git a/src/main/scala/li/cil/oc/common/event/RackMountableRenderHandler.scala b/src/main/scala/li/cil/oc/common/event/RackMountableRenderHandler.scala index f525a42fd..8f63d93ba 100644 --- a/src/main/scala/li/cil/oc/common/event/RackMountableRenderHandler.scala +++ b/src/main/scala/li/cil/oc/common/event/RackMountableRenderHandler.scala @@ -75,9 +75,12 @@ object RackMountableRenderHandler { if (e.data.getBoolean("hasErrored") && RenderUtil.shouldShowErrorLight(e.rack.hashCode * (e.mountable + 1))) { e.renderOverlay(Textures.blockRackServerError) } - if (System.currentTimeMillis() - e.data.getLong("lastAccess") < 400 && e.rack.world.rand.nextDouble() > 0.1) { + if (System.currentTimeMillis() - e.data.getLong("lastFileSystemAccess") < 400 && e.rack.world.rand.nextDouble() > 0.1) { e.renderOverlay(Textures.blockRackServerActivity) } + if (!(System.currentTimeMillis() - e.data.getLong("lastNetworkActivity") < 300 && System.currentTimeMillis() % 200 > 100) && e.data.getBoolean("isRunning")) { + e.renderOverlay(Textures.blockRackServerNetworkActivity) + } RenderState.enableLighting() } diff --git a/src/main/scala/li/cil/oc/common/tileentity/Case.scala b/src/main/scala/li/cil/oc/common/tileentity/Case.scala index 2c37414f2..2904bc106 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/Case.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/Case.scala @@ -19,8 +19,9 @@ import net.minecraftforge.common.util.ForgeDirection class Case(var tier: Int) extends traits.PowerAcceptor with traits.Computer with traits.Colored with internal.Case { def this() = this(0) - // Used on client side to check whether to render disk activity indicators. - var lastAccess = 0L + // Used on client side to check whether to render disk activity/network indicators. + var lastFileSystemAccess = 0L + var lastNetworkActivity = 0L color = Color.byTier(tier) diff --git a/src/main/scala/li/cil/oc/integration/opencomputers/ModOpenComputers.scala b/src/main/scala/li/cil/oc/integration/opencomputers/ModOpenComputers.scala index 7ce829940..98eae5eec 100644 --- a/src/main/scala/li/cil/oc/integration/opencomputers/ModOpenComputers.scala +++ b/src/main/scala/li/cil/oc/integration/opencomputers/ModOpenComputers.scala @@ -86,6 +86,7 @@ object ModOpenComputers extends ModProxy { MinecraftForge.EVENT_BUS.register(HoverBootsHandler) MinecraftForge.EVENT_BUS.register(Loot) MinecraftForge.EVENT_BUS.register(NanomachinesHandler.Common) + MinecraftForge.EVENT_BUS.register(NetworkActivityHandler) MinecraftForge.EVENT_BUS.register(RobotCommonHandler) MinecraftForge.EVENT_BUS.register(SaveHandler) MinecraftForge.EVENT_BUS.register(Tablet) diff --git a/src/main/scala/li/cil/oc/server/PacketSender.scala b/src/main/scala/li/cil/oc/server/PacketSender.scala index bf9a8e664..07c3a5828 100644 --- a/src/main/scala/li/cil/oc/server/PacketSender.scala +++ b/src/main/scala/li/cil/oc/server/PacketSender.scala @@ -1,7 +1,7 @@ package li.cil.oc.server import li.cil.oc.api -import li.cil.oc.api.event.FileSystemAccessEvent +import li.cil.oc.api.event.{NetworkActivityEvent, FileSystemAccessEvent} import li.cil.oc.api.network.EnvironmentHost import li.cil.oc.api.network.Node import li.cil.oc.common._ @@ -135,6 +135,34 @@ object PacketSender { } } + def sendNetworkActivity(node: Node, host: EnvironmentHost) = { + + val event = host match { + case t: net.minecraft.tileentity.TileEntity => new NetworkActivityEvent.Server(t, node) + case _ => new NetworkActivityEvent.Server(host.world, host.xPosition, host.yPosition, host.zPosition, node) + } + MinecraftForge.EVENT_BUS.post(event) + if (!event.isCanceled) { + + val pb = new SimplePacketBuilder(PacketType.NetworkActivity) + + CompressedStreamTools.write(event.getData, pb) + event.getTileEntity match { + case t: net.minecraft.tileentity.TileEntity => + pb.writeBoolean(true) + pb.writeTileEntity(t) + case _ => + pb.writeBoolean(false) + pb.writeInt(event.getWorld.provider.dimensionId) + pb.writeDouble(event.getX) + pb.writeDouble(event.getY) + pb.writeDouble(event.getZ) + } + + pb.sendToPlayersNearHost(host, Option(64)) + } + } + def sendFloppyChange(t: tileentity.DiskDrive, stack: ItemStack = null) { val pb = new SimplePacketBuilder(PacketType.FloppyChange) diff --git a/src/main/scala/li/cil/oc/server/component/NetworkCard.scala b/src/main/scala/li/cil/oc/server/component/NetworkCard.scala index 9dd697207..591effe42 100644 --- a/src/main/scala/li/cil/oc/server/component/NetworkCard.scala +++ b/src/main/scala/li/cil/oc/server/component/NetworkCard.scala @@ -12,6 +12,7 @@ import li.cil.oc.api.machine.Context import li.cil.oc.api.network.EnvironmentHost import li.cil.oc.api.network._ import li.cil.oc.api.prefab +import li.cil.oc.server.{PacketSender => ServerPacketSender} import net.minecraft.nbt._ import scala.collection.convert.WrapAsScala._ @@ -73,6 +74,7 @@ class NetworkCard(val host: EnvironmentHost) extends prefab.ManagedEnvironment w val port = checkPort(args.checkInteger(1)) val packet = api.Network.newPacket(node.address, address, port, args.drop(2).toArray) doSend(packet) + networkActivity() result(true) } @@ -81,6 +83,7 @@ class NetworkCard(val host: EnvironmentHost) extends prefab.ManagedEnvironment w val port = checkPort(args.checkInteger(0)) val packet = api.Network.newPacket(node.address, null, port, args.drop(1).toArray) doBroadcast(packet) + networkActivity() result(true) } @@ -137,6 +140,7 @@ class NetworkCard(val host: EnvironmentHost) extends prefab.ManagedEnvironment w if (packet.source != node.address && Option(packet.destination).forall(_ == node.address)) { if (openPorts.contains(packet.port)) { node.sendToReachable("computer.signal", Seq("modem_message", packet.source, Int.box(packet.port), Double.box(distance)) ++ packet.data: _*) + networkActivity() } // Accept wake-up messages regardless of port because we close all ports // when our computer shuts down. @@ -184,4 +188,11 @@ class NetworkCard(val host: EnvironmentHost) extends prefab.ManagedEnvironment w protected def checkPort(port: Int) = if (port < 1 || port > 0xFFFF) throw new IllegalArgumentException("invalid port number") else port + + private def networkActivity() { + host match { + case (h) => ServerPacketSender.sendNetworkActivity(node, h) + case _ => + } + } } diff --git a/src/main/scala/li/cil/oc/server/component/Server.scala b/src/main/scala/li/cil/oc/server/component/Server.scala index f5cedf57b..339fb3bd6 100644 --- a/src/main/scala/li/cil/oc/server/component/Server.scala +++ b/src/main/scala/li/cil/oc/server/component/Server.scala @@ -38,7 +38,8 @@ class Server(val rack: tileentity.Rack, val slot: Int) extends Environment with var wasRunning = false var hadErrored = false - var lastAccess = 0L + var lastFileSystemAccess = 0L + var lastNetworkActivity = 0L // ----------------------------------------------------------------------- // // Environment @@ -142,7 +143,8 @@ class Server(val rack: tileentity.Rack, val slot: Int) extends Environment with val nbt = new NBTTagCompound() nbt.setBoolean("isRunning", wasRunning) nbt.setBoolean("hasErrored", hadErrored) - nbt.setLong("lastAccess", lastAccess) + nbt.setLong("lastFileSystemAccess", lastFileSystemAccess) + nbt.setLong("lastNetworkActivity", lastNetworkActivity) nbt }