From f69250b565e4398ed64a0ff04a88eb72b8f28929 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Mon, 2 Feb 2015 23:05:21 +0100 Subject: [PATCH] Added ability to copy lines from screens using the analyzer (shift-rightclicking the line with the analyzer), then paste it on a screen by simply rightclicking it with the analyzer. Closes #644. --- .../scala/li/cil/oc/client/PacketSender.scala | 9 ++++ .../scala/li/cil/oc/common/PacketType.scala | 1 + .../scala/li/cil/oc/common/block/Screen.scala | 8 +++- .../cil/oc/common/component/TextBuffer.scala | 31 +++++++++++++ .../li/cil/oc/common/item/Analyzer.scala | 29 ++++++++++++- .../li/cil/oc/common/tileentity/Screen.scala | 43 +++++++++++++------ .../li/cil/oc/server/PacketHandler.scala | 8 ++++ 7 files changed, 114 insertions(+), 15 deletions(-) diff --git a/src/main/scala/li/cil/oc/client/PacketSender.scala b/src/main/scala/li/cil/oc/client/PacketSender.scala index 640aedd53..3ee46208f 100644 --- a/src/main/scala/li/cil/oc/client/PacketSender.scala +++ b/src/main/scala/li/cil/oc/client/PacketSender.scala @@ -110,6 +110,15 @@ object PacketSender { pb.sendToServer() } + def sendCopyToAnalyzer(address: String, line: Int): Unit = { + val pb = new SimplePacketBuilder(PacketType.CopyToAnalyzer) + + pb.writeUTF(address) + pb.writeInt(line) + + pb.sendToServer() + } + def sendMultiPlace() { val pb = new SimplePacketBuilder(PacketType.MultiPartPlace) pb.sendToServer() diff --git a/src/main/scala/li/cil/oc/common/PacketType.scala b/src/main/scala/li/cil/oc/common/PacketType.scala index c925def9d..6a0a0f201 100644 --- a/src/main/scala/li/cil/oc/common/PacketType.scala +++ b/src/main/scala/li/cil/oc/common/PacketType.scala @@ -52,6 +52,7 @@ object PacketType extends Enumeration { // Client -> Server ComputerPower, + CopyToAnalyzer, DronePower, KeyDown, KeyUp, diff --git a/src/main/scala/li/cil/oc/common/block/Screen.scala b/src/main/scala/li/cil/oc/common/block/Screen.scala index fce0e3c5a..7944f269c 100644 --- a/src/main/scala/li/cil/oc/common/block/Screen.scala +++ b/src/main/scala/li/cil/oc/common/block/Screen.scala @@ -6,10 +6,12 @@ import cpw.mods.fml.relauncher.Side import cpw.mods.fml.relauncher.SideOnly import li.cil.oc.OpenComputers import li.cil.oc.Settings +import li.cil.oc.api import li.cil.oc.common.GuiType import li.cil.oc.common.tileentity import li.cil.oc.integration.util.Wrench import li.cil.oc.util._ +import net.minecraft.client.Minecraft import net.minecraft.client.renderer.texture.IIconRegister import net.minecraft.entity.Entity import net.minecraft.entity.EntityLivingBase @@ -332,6 +334,7 @@ class Screen(val tier: Int) extends RedstoneAware { def rightClick(world: World, x: Int, y: Int, z: Int, player: EntityPlayer, side: ForgeDirection, hitX: Float, hitY: Float, hitZ: Float, force: Boolean) = { if (Wrench.holdsApplicableWrench(player, BlockPosition(x, y, z)) && getValidRotations(world, x, y, z).contains(side) && !force) false + else if (api.Items.get(player.getHeldItem) == api.Items.get("analyzer")) false else world.getTileEntity(x, y, z) match { case screen: tileentity.Screen if screen.hasKeyboard && (force || player.isSneaking == screen.invertTouchMode) => // Yep, this GUI is actually purely client side. We could skip this @@ -342,7 +345,10 @@ class Screen(val tier: Int) extends RedstoneAware { } true case screen: tileentity.Screen if screen.tier > 0 && side == screen.facing => - screen.click(player, hitX, hitY, hitZ) + if (world.isRemote && player == Minecraft.getMinecraft.thePlayer) { + screen.click(hitX, hitY, hitZ) + } + else true case _ => false } } diff --git a/src/main/scala/li/cil/oc/common/component/TextBuffer.scala b/src/main/scala/li/cil/oc/common/component/TextBuffer.scala index be1654486..4c9a1d243 100644 --- a/src/main/scala/li/cil/oc/common/component/TextBuffer.scala +++ b/src/main/scala/li/cil/oc/common/component/TextBuffer.scala @@ -429,6 +429,10 @@ class TextBuffer(val host: EnvironmentHost) extends prefab.ManagedEnvironment wi override def mouseScroll(x: Int, y: Int, delta: Int, player: EntityPlayer) = mouseScroll(x, y, delta, player) + def copyToAnalyzer(line: Int, player: EntityPlayer): Unit = { + proxy.copyToAnalyzer(line, player) + } + // ----------------------------------------------------------------------- // override def onConnect(node: Node) { @@ -602,6 +606,8 @@ object TextBuffer { def mouseUp(x: Double, y: Double, button: Int, player: EntityPlayer): Unit def mouseScroll(x: Double, y: Double, delta: Int, player: EntityPlayer): Unit + + def copyToAnalyzer(line: Int, player: EntityPlayer): Unit } class ClientProxy(val owner: TextBuffer) extends Proxy { @@ -686,6 +692,10 @@ object TextBuffer { ClientPacketSender.sendMouseScroll(nodeAddress, x, y, delta) } + override def copyToAnalyzer(line: Int, player: EntityPlayer): Unit = { + ClientPacketSender.sendCopyToAnalyzer(nodeAddress, line) + } + private lazy val Debugger = api.Items.get("debugger") private def debug(message: String) { @@ -789,6 +799,27 @@ object TextBuffer { sendMouseEvent(player, "scroll", x, y, delta) } + override def copyToAnalyzer(line: Int, player: EntityPlayer): Unit = { + val stack = player.getHeldItem + if (stack != null) { + if (!stack.hasTagCompound) { + stack.setTagCompound(new NBTTagCompound()) + } + stack.getTagCompound.removeTag(Settings.namespace + "clipboard") + + if (line >= 0 && line < owner.data.height) { + val text = new String(owner.data.buffer(line)).trim + if (!Strings.isNullOrEmpty(text)) { + stack.getTagCompound.setString(Settings.namespace + "clipboard", text) + } + } + + if (stack.getTagCompound.hasNoTags) { + stack.setTagCompound(null) + } + } + } + private def sendMouseEvent(player: EntityPlayer, name: String, x: Double, y: Double, data: Int) = { val args = mutable.ArrayBuffer.empty[AnyRef] diff --git a/src/main/scala/li/cil/oc/common/item/Analyzer.scala b/src/main/scala/li/cil/oc/common/item/Analyzer.scala index 89b4e5df4..b12b871d5 100644 --- a/src/main/scala/li/cil/oc/common/item/Analyzer.scala +++ b/src/main/scala/li/cil/oc/common/item/Analyzer.scala @@ -2,15 +2,18 @@ package li.cil.oc.common.item import cpw.mods.fml.common.eventhandler.SubscribeEvent import li.cil.oc.Localization +import li.cil.oc.Settings import li.cil.oc.api import li.cil.oc.api.network.Analyzable import li.cil.oc.api.network._ +import li.cil.oc.common.tileentity import li.cil.oc.server.PacketSender import li.cil.oc.util.BlockPosition import li.cil.oc.util.ExtendedWorld._ import net.minecraft.entity.player.EntityPlayer import net.minecraft.entity.player.EntityPlayerMP import net.minecraft.item.ItemStack +import net.minecraft.world.World import net.minecraftforge.common.util.ForgeDirection import net.minecraftforge.event.entity.player.EntityInteractEvent @@ -79,7 +82,31 @@ object Analyzer { } class Analyzer(val parent: Delegator) extends Delegate { + override def onItemRightClick(stack: ItemStack, world: World, player: EntityPlayer): ItemStack = { + if (player.isSneaking && stack.hasTagCompound) { + stack.getTagCompound.removeTag(Settings.namespace + "clipboard") + if (stack.getTagCompound.hasNoTags) { + stack.setTagCompound(null) + } + } + super.onItemRightClick(stack, world, player) + } + override def onItemUse(stack: ItemStack, player: EntityPlayer, position: BlockPosition, side: Int, hitX: Float, hitY: Float, hitZ: Float) = { - Analyzer.analyze(position.world.get.getTileEntity(position), player, side, hitX, hitY, hitZ) + val world = player.getEntityWorld + world.getTileEntity(position) match { + case screen: tileentity.Screen if ForgeDirection.getOrientation(side) == screen.facing => + if (player.isSneaking) { + screen.copyToAnalyzer(hitX, hitY, hitZ) + } + else if (stack.hasTagCompound && stack.getTagCompound.hasKey(Settings.namespace + "clipboard")) { + if (!world.isRemote) { + screen.origin.buffer.clipboard(stack.getTagCompound.getString(Settings.namespace + "clipboard"), player) + } + true + } + else false + case _ => Analyzer.analyze(position.world.get.getTileEntity(position), player, side, hitX, hitY, hitZ) + } } } diff --git a/src/main/scala/li/cil/oc/common/tileentity/Screen.scala b/src/main/scala/li/cil/oc/common/tileentity/Screen.scala index ffac38a2e..55dbd3ca7 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/Screen.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/Screen.scala @@ -5,6 +5,7 @@ import cpw.mods.fml.relauncher.SideOnly import li.cil.oc.Settings import li.cil.oc.api.network.Analyzable import li.cil.oc.api.network._ +import li.cil.oc.common.component.TextBuffer import li.cil.oc.util.Color import net.minecraft.client.Minecraft import net.minecraft.entity.Entity @@ -93,7 +94,7 @@ class Screen(var tier: Int) extends traits.TextBuffer with SidedEnvironment with invertTouchMode = false } - def click(player: EntityPlayer, hitX: Double, hitY: Double, hitZ: Double): Boolean = { + def toScreenCoordinates(hitX: Double, hitY: Double, hitZ: Double): (Boolean, Option[(Double, Double)]) = { // Compute absolute position of the click on the face, measured in blocks. def dot(f: ForgeDirection) = f.offsetX * hitX + f.offsetY * hitY + f.offsetZ * hitZ val (hx, hy) = (dot(toGlobal(ForgeDirection.EAST)), dot(toGlobal(ForgeDirection.UP))) @@ -105,9 +106,9 @@ class Screen(var tier: Int) extends traits.TextBuffer with SidedEnvironment with // Get the relative position in the *display area* of the face. val border = 2.25 / 16.0 if (ax <= border || ay <= border || ax >= width - border || ay >= height - border) { - return false + return (false, None) } - if (!world.isRemote) return true + if (!world.isRemote) return (true, None) val (iw, ih) = (width - border * 2, height - border * 2) val (rx, ry) = ((ax - border) / iw, (ay - border) / ih) @@ -119,27 +120,43 @@ class Screen(var tier: Int) extends traits.TextBuffer with SidedEnvironment with val (brx, bry) = if (bpw > bph) { val rh = bph.toDouble / bpw.toDouble val bry = (ry - (1 - rh) * 0.5) / rh - if (bry <= 0 || bry >= 1) { - return true - } (rx, bry) } else if (bph > bpw) { val rw = bpw.toDouble / bph.toDouble val brx = (rx - (1 - rw) * 0.5) / rw - if (brx <= 0 || brx >= 1) { - return true - } (brx, ry) } else { (rx, ry) } - // Convert to absolute coordinates and send the packet to the server. - origin.buffer.mouseDown(brx * bw, bry * bh, 0, null) + val inBounds = bry >= 0 && bry <= 1 && brx >= 0 || brx <= 1 + (inBounds, Some((brx * bw, bry * bh))) + } - true + def copyToAnalyzer(hitX: Double, hitY: Double, hitZ: Double): Boolean = { + val (inBounds, coordinates) = toScreenCoordinates(hitX, hitY, hitZ) + coordinates match { + case Some((x, y)) => origin.buffer match { + case buffer: TextBuffer => + buffer.copyToAnalyzer(y.toInt, null) + true + case _ => false + } + case _ => inBounds + } + } + + def click(hitX: Double, hitY: Double, hitZ: Double): Boolean = { + val (inBounds, coordinates) = toScreenCoordinates(hitX, hitY, hitZ) + coordinates match { + case Some((x, y)) => + // Send the packet to the server (manually, for accuracy). + origin.buffer.mouseDown(x, y, 0, null) + true + case _ => inBounds + } } def walk(entity: Entity) { @@ -236,7 +253,7 @@ class Screen(var tier: Int) extends traits.TextBuffer with SidedEnvironment with hitXInner && !hitYInner && hitZInner || !hitXInner && hitYInner && hitZInner) { arrow.shootingEntity match { - case player: EntityPlayer if player == Minecraft.getMinecraft.thePlayer => click(player, hitX, hitY, hitZ) + case player: EntityPlayer if player == Minecraft.getMinecraft.thePlayer => click(hitX, hitY, hitZ) case _ => } } diff --git a/src/main/scala/li/cil/oc/server/PacketHandler.scala b/src/main/scala/li/cil/oc/server/PacketHandler.scala index 1b6e92cd0..ba005a7fb 100644 --- a/src/main/scala/li/cil/oc/server/PacketHandler.scala +++ b/src/main/scala/li/cil/oc/server/PacketHandler.scala @@ -32,6 +32,7 @@ object PacketHandler extends CommonPacketHandler { override def dispatch(p: PacketParser) { p.packetType match { case PacketType.ComputerPower => onComputerPower(p) + case PacketType.CopyToAnalyzer => onCopyToAnalyzer(p) case PacketType.DronePower => onDronePower(p) case PacketType.KeyDown => onKeyDown(p) case PacketType.KeyUp => onKeyUp(p) @@ -67,6 +68,13 @@ object PacketHandler extends CommonPacketHandler { case _ => // Invalid packet. } + def onCopyToAnalyzer(p: PacketParser) { + ComponentTracker.get(p.player.worldObj, p.readUTF()) match { + case Some(buffer: TextBuffer) => buffer.copyToAnalyzer(p.readInt(), p.player.asInstanceOf[EntityPlayer]) + case _ => // Invalid Packet + } + } + def onDronePower(p: PacketParser) = p.readEntity[Drone]() match { case Some(drone) => p.player match {