diff --git a/src/main/java/li/cil/oc/api/machine/Context.java b/src/main/java/li/cil/oc/api/machine/Context.java index 622696e2a..fe82dacb6 100644 --- a/src/main/java/li/cil/oc/api/machine/Context.java +++ b/src/main/java/li/cil/oc/api/machine/Context.java @@ -153,6 +153,7 @@ public interface Context { *
  • Strings.
  • *
  • Byte arrays (which appear as strings on the Lua side, e.g.).
  • *
  • Maps if and only if both keys and values are strings.
  • + *
  • NBTTagCompounds.
  • * * If an unsupported type is specified the method will enqueue nothing * instead, resulting in a nil on the Lua side, e.g., and log a diff --git a/src/main/scala/li/cil/oc/client/PacketSender.scala b/src/main/scala/li/cil/oc/client/PacketSender.scala index 59590a8df..22a3a9efd 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/client/renderer/tileentity/RobotRenderer.scala b/src/main/scala/li/cil/oc/client/renderer/tileentity/RobotRenderer.scala index 5384f8952..c026f22af 100644 --- a/src/main/scala/li/cil/oc/client/renderer/tileentity/RobotRenderer.scala +++ b/src/main/scala/li/cil/oc/client/renderer/tileentity/RobotRenderer.scala @@ -284,7 +284,7 @@ object RobotRenderer extends TileEntitySpecialRenderer { val timeJitter = robot.hashCode ^ 0xFF val hover = - if (robot.isRunning) (Math.sin(timeJitter + (worldTime + f) / 20.0) * 0.03).toFloat + if (robot.isRunning) (Math.sin(timeJitter + worldTime / 20.0) * 0.03).toFloat else -0.03f GL11.glTranslatef(0, hover, 0) diff --git a/src/main/scala/li/cil/oc/common/EventHandler.scala b/src/main/scala/li/cil/oc/common/EventHandler.scala index b1419570f..b4f535e07 100644 --- a/src/main/scala/li/cil/oc/common/EventHandler.scala +++ b/src/main/scala/li/cil/oc/common/EventHandler.scala @@ -19,6 +19,7 @@ import net.minecraft.item.ItemStack import net.minecraft.server.MinecraftServer import net.minecraft.tileentity.TileEntity import net.minecraftforge.common.util.FakePlayer +import net.minecraftforge.event.world.BlockEvent import net.minecraftforge.event.world.WorldEvent import net.minecraftforge.fml.common.eventhandler.SubscribeEvent import net.minecraftforge.fml.common.gameevent.PlayerEvent._ @@ -114,6 +115,22 @@ object EventHandler { } } + @SubscribeEvent + def onBlockBreak(e: BlockEvent.BreakEvent): Unit = { + e.world.getTileEntity(e.pos) match { + case c: tileentity.Case => + if (c.isCreative && (!e.getPlayer.capabilities.isCreativeMode || !c.canInteract(e.getPlayer.getName))) { + e.setCanceled(true) + } + case r: tileentity.RobotProxy => + val robot = r.robot + if (robot.isCreative && (!e.getPlayer.capabilities.isCreativeMode || !robot.canInteract(e.getPlayer.getName))) { + e.setCanceled(true) + } + case _ => + } + } + lazy val drone = api.Items.get("drone") lazy val eeprom = api.Items.get("eeprom") lazy val mcu = api.Items.get("microcontroller") diff --git a/src/main/scala/li/cil/oc/common/PacketType.scala b/src/main/scala/li/cil/oc/common/PacketType.scala index ee8251214..448070068 100644 --- a/src/main/scala/li/cil/oc/common/PacketType.scala +++ b/src/main/scala/li/cil/oc/common/PacketType.scala @@ -51,6 +51,7 @@ object PacketType extends Enumeration { // Client -> Server ComputerPower, + CopyToAnalyzer, DronePower, KeyDown, KeyUp, diff --git a/src/main/scala/li/cil/oc/common/block/Case.scala b/src/main/scala/li/cil/oc/common/block/Case.scala index 4042703aa..ca8bd32e5 100644 --- a/src/main/scala/li/cil/oc/common/block/Case.scala +++ b/src/main/scala/li/cil/oc/common/block/Case.scala @@ -86,7 +86,9 @@ class Case(val tier: Int) extends RedstoneAware with traits.PowerAcceptor with t override def removedByPlayer(world: World, pos: BlockPos, player: EntityPlayer, willHarvest: Boolean) = world.getTileEntity(pos) match { - case c: tileentity.Case => c.canInteract(player.getName) && super.removedByPlayer(world, pos, player, willHarvest) + case c: tileentity.Case => + if (c.isCreative && (!player.capabilities.isCreativeMode || !c.canInteract(player.getName))) false + else c.canInteract(player.getName) && super.removedByPlayer(world, pos, player, willHarvest) case _ => super.removedByPlayer(world, pos, player, willHarvest) } } diff --git a/src/main/scala/li/cil/oc/common/block/RobotProxy.scala b/src/main/scala/li/cil/oc/common/block/RobotProxy.scala index bd10b19f2..dacf057cc 100644 --- a/src/main/scala/li/cil/oc/common/block/RobotProxy.scala +++ b/src/main/scala/li/cil/oc/common/block/RobotProxy.scala @@ -230,6 +230,11 @@ class RobotProxy extends RedstoneAware with traits.StateAware { world.getTileEntity(pos) match { case proxy: tileentity.RobotProxy => val robot = proxy.robot + // Only allow breaking creative tier robots by allowed users. + // Unlike normal robots, griefing isn't really a valid concern + // here, because to get a creative robot you need creative + // mode in the first place. + if (robot.isCreative && (!player.capabilities.isCreativeMode || !robot.canInteract(player.getName))) return false if (!world.isRemote) { if (robot.player == player) return false robot.node.remove() 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 b2b3656d1..6a4bb29b4 100644 --- a/src/main/scala/li/cil/oc/common/block/Screen.scala +++ b/src/main/scala/li/cil/oc/common/block/Screen.scala @@ -4,6 +4,7 @@ import java.util 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 @@ -13,6 +14,7 @@ import li.cil.oc.util.Rarity import li.cil.oc.util.Tooltip import net.minecraft.block.properties.IProperty import net.minecraft.block.state.IBlockState +import net.minecraft.client.Minecraft import net.minecraft.entity.Entity import net.minecraft.entity.EntityLivingBase import net.minecraft.entity.player.EntityPlayer @@ -88,6 +90,7 @@ class Screen(val tier: Int) extends RedstoneAware with traits.OmniRotatable { def rightClick(world: World, pos: BlockPos, player: EntityPlayer, side: EnumFacing, hitX: Float, hitY: Float, hitZ: Float, force: Boolean) = { if (Wrench.holdsApplicableWrench(player, pos) && getValidRotations(world, pos).contains(side) && !force) false + else if (api.Items.get(player.getHeldItem) == api.Items.get("analyzer")) false else world.getTileEntity(pos) 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 @@ -98,7 +101,10 @@ class Screen(val tier: Int) extends RedstoneAware with traits.OmniRotatable { } 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/Terminal.scala b/src/main/scala/li/cil/oc/common/component/Terminal.scala index e2f812b64..fc8c0c862 100644 --- a/src/main/scala/li/cil/oc/common/component/Terminal.scala +++ b/src/main/scala/li/cil/oc/common/component/Terminal.scala @@ -6,6 +6,7 @@ import li.cil.oc.api.component.Keyboard.UsabilityChecker import li.cil.oc.api.network.Component import li.cil.oc.api.network.Node import li.cil.oc.api.network.Visibility +import li.cil.oc.common.Tier import li.cil.oc.common.item import li.cil.oc.common.item.Delegator import li.cil.oc.common.tileentity @@ -23,9 +24,9 @@ class Terminal(val rack: tileentity.ServerRack, val number: Int) { val buffer = { val screenItem = api.Items.get("screen1").createItemStack(1) val buffer = api.Driver.driverFor(screenItem, rack.getClass).createEnvironment(screenItem, rack).asInstanceOf[api.component.TextBuffer] - val (maxWidth, maxHeight) = Settings.screenResolutionsByTier(1) + val (maxWidth, maxHeight) = Settings.screenResolutionsByTier(Tier.Three) buffer.setMaximumResolution(maxWidth, maxHeight) - buffer.setMaximumColorDepth(Settings.screenDepthsByTier(1)) + buffer.setMaximumColorDepth(Settings.screenDepthsByTier(Tier.Three)) buffer } 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 d7a660e98..56d50ba9f 100644 --- a/src/main/scala/li/cil/oc/common/component/TextBuffer.scala +++ b/src/main/scala/li/cil/oc/common/component/TextBuffer.scala @@ -415,6 +415,10 @@ class TextBuffer(val host: EnvironmentHost) extends prefab.ManagedEnvironment wi override def mouseScroll(x: Double, y: Double, delta: Int, player: EntityPlayer) = proxy.mouseScroll(x, y, delta, player) + def copyToAnalyzer(line: Int, player: EntityPlayer): Unit = { + proxy.copyToAnalyzer(line, player) + } + // ----------------------------------------------------------------------- // override def onConnect(node: Node) { @@ -588,6 +592,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 { @@ -672,6 +678,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) { @@ -775,6 +785,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 e762790ec..d54f088eb 100644 --- a/src/main/scala/li/cil/oc/common/item/Analyzer.scala +++ b/src/main/scala/li/cil/oc/common/item/Analyzer.scala @@ -1,9 +1,11 @@ package li.cil.oc.common.item 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._ @@ -11,6 +13,7 @@ import net.minecraft.entity.player.EntityPlayer import net.minecraft.entity.player.EntityPlayerMP import net.minecraft.item.ItemStack import net.minecraft.util.EnumFacing +import net.minecraft.world.World import net.minecraftforge.event.entity.player.EntityInteractEvent import net.minecraftforge.fml.common.eventhandler.SubscribeEvent @@ -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: EnumFacing, 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 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/item/Tablet.scala b/src/main/scala/li/cil/oc/common/item/Tablet.scala index bb63133b5..7f6adeb55 100644 --- a/src/main/scala/li/cil/oc/common/item/Tablet.scala +++ b/src/main/scala/li/cil/oc/common/item/Tablet.scala @@ -29,6 +29,7 @@ import li.cil.oc.common.inventory.ComponentInventory import li.cil.oc.common.item.data.TabletData import li.cil.oc.integration.opencomputers.DriverScreen import li.cil.oc.server.component +import li.cil.oc.util.BlockPosition import li.cil.oc.util.ExtendedNBT._ import li.cil.oc.util.Rarity import li.cil.oc.util.RotationHelper @@ -93,6 +94,24 @@ class Tablet(val parent: Delegator) extends Delegate { case _ => } + override def onItemUse(stack: ItemStack, player: EntityPlayer, position: BlockPosition, side: EnumFacing, hitX: Float, hitY: Float, hitZ: Float): Boolean = { + val world = player.getEntityWorld + if (!world.isRemote) try { + val computer = Tablet.get(stack, player).machine + if (computer.isRunning) { + val data = new NBTTagCompound() + computer.node.sendToReachable("tablet.use", data, stack, player, position, side, float2Float(hitX), float2Float(hitY), float2Float(hitZ)) + if (!data.hasNoTags) { + computer.signal("tablet_use", data) + } + } + } + catch { + case t: Throwable => OpenComputers.log.warn("Block analysis on tablet right click failed gloriously!", t) + } + true + } + override def onItemRightClick(stack: ItemStack, world: World, player: EntityPlayer) = { if (!player.isSneaking) { if (world.isRemote) { 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 72b010f18..d0a57e8d2 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/Case.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/Case.scala @@ -35,7 +35,7 @@ class Case(var tier: Int) extends traits.PowerAcceptor with traits.Computer with var maxComponents = 0 - private def isCreativeCase = tier == Tier.Four + def isCreative = tier == Tier.Four // ----------------------------------------------------------------------- // @@ -74,7 +74,7 @@ class Case(var tier: Int) extends traits.PowerAcceptor with traits.Computer with override def canUpdate = isServer override def updateEntity() { - if (isServer && isCreativeCase && world.getTotalWorldTime % Settings.get.tickFrequency == 0) { + if (isServer && isCreative && world.getTotalWorldTime % Settings.get.tickFrequency == 0) { // Creative case, make it generate power. node.asInstanceOf[Connector].changeBuffer(Double.PositiveInfinity) } @@ -135,7 +135,7 @@ class Case(var tier: Int) extends traits.PowerAcceptor with traits.Computer with override def getSizeInventory = if (tier < 0 || tier >= InventorySlots.computer.length) 0 else InventorySlots.computer(tier).length override def isUseableByPlayer(player: EntityPlayer) = - super.isUseableByPlayer(player) && (!isCreativeCase || player.capabilities.isCreativeMode) + super.isUseableByPlayer(player) && (!isCreative || player.capabilities.isCreativeMode) override def isItemValidForSlot(slot: Int, stack: ItemStack) = Option(Driver.driverFor(stack, getClass)).fold(false)(driver => { diff --git a/src/main/scala/li/cil/oc/common/tileentity/Robot.scala b/src/main/scala/li/cil/oc/common/tileentity/Robot.scala index 6c0b69b49..ce97a8340 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/Robot.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/Robot.scala @@ -70,6 +70,8 @@ class Robot extends traits.Computer with traits.PowerInformation with traits.Rot override def tier = info.tier + def isCreative = tier == Tier.Four + // Wrapper for the part of the inventory that is mutable. val dynamicInventory = new IInventory { override def getSizeInventory = Robot.this.inventorySize @@ -749,6 +751,9 @@ class Robot extends traits.Computer with traits.PowerInformation with traits.Rot else if (stack != null && stack.stackSize > 0) spawnStackInWorld(stack, Option(EnumFacing.UP)) } + override def isUseableByPlayer(player: EntityPlayer) = + super.isUseableByPlayer(player) && (!isCreative || player.capabilities.isCreativeMode) + override def isItemValidForSlot(slot: Int, stack: ItemStack) = (slot, Option(Driver.driverFor(stack, getClass))) match { case (0, _) => true // Allow anything in the tool slot. case (i, Some(driver)) if isContainerSlot(i) => 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 1db36b52f..b3308b14d 100644 --- a/src/main/scala/li/cil/oc/common/tileentity/Screen.scala +++ b/src/main/scala/li/cil/oc/common/tileentity/Screen.scala @@ -3,6 +3,7 @@ package li.cil.oc.common.tileentity 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.BlockPosition import li.cil.oc.util.Color import li.cil.oc.util.ExtendedWorld._ @@ -95,7 +96,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: EnumFacing) = f.getFrontOffsetX * hitX + f.getFrontOffsetY * hitY + f.getFrontOffsetZ * hitZ val (hx, hy) = (dot(toGlobal(EnumFacing.EAST)), dot(toGlobal(EnumFacing.UP))) @@ -107,9 +108,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) @@ -121,27 +122,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) { @@ -238,7 +255,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/integration/vanilla/ConverterNBT.scala b/src/main/scala/li/cil/oc/integration/vanilla/ConverterNBT.scala new file mode 100644 index 000000000..056b0702b --- /dev/null +++ b/src/main/scala/li/cil/oc/integration/vanilla/ConverterNBT.scala @@ -0,0 +1,35 @@ +package li.cil.oc.integration.vanilla + +import java.util + +import li.cil.oc.api +import net.minecraft.nbt._ + +import scala.collection.convert.WrapAsScala._ + +object ConverterNBT extends api.driver.Converter { + override def convert(value: AnyRef, output: util.Map[AnyRef, AnyRef]) = + value match { + case nbt: NBTTagCompound => output += "oc:flatten" -> convert(nbt) + case _ => + } + + private def convert(nbt: NBTBase): AnyRef = nbt match { + case tag: NBTTagByte => byte2Byte(tag.getByte) + case tag: NBTTagShort => short2Short(tag.getShort) + case tag: NBTTagInt => int2Integer(tag.getInt) + case tag: NBTTagLong => long2Long(tag.getLong) + case tag: NBTTagFloat => float2Float(tag.getFloat) + case tag: NBTTagDouble => double2Double(tag.getDouble) + case tag: NBTTagByteArray => tag.getByteArray + case tag: NBTTagString => tag.getString + case tag: NBTTagList => + val copy = tag.copy().asInstanceOf[NBTTagList] + (0 until copy.tagCount).map(_ => convert(copy.removeTag(0))).toArray + case tag: NBTTagCompound => + tag.getKeySet.collect { + case key: String => key -> convert(tag.getTag(key)) + }.toMap + case tag: NBTTagIntArray => tag.getIntArray + } +} diff --git a/src/main/scala/li/cil/oc/integration/vanilla/ModVanilla.scala b/src/main/scala/li/cil/oc/integration/vanilla/ModVanilla.scala index cf98113bf..f525cc064 100644 --- a/src/main/scala/li/cil/oc/integration/vanilla/ModVanilla.scala +++ b/src/main/scala/li/cil/oc/integration/vanilla/ModVanilla.scala @@ -31,5 +31,6 @@ object ModVanilla extends ModProxy { Driver.add(ConverterFluidStack) Driver.add(ConverterFluidTankInfo) Driver.add(ConverterItemStack) + Driver.add(ConverterNBT) } } diff --git a/src/main/scala/li/cil/oc/server/PacketHandler.scala b/src/main/scala/li/cil/oc/server/PacketHandler.scala index cddd4ef2b..4b6860891 100644 --- a/src/main/scala/li/cil/oc/server/PacketHandler.scala +++ b/src/main/scala/li/cil/oc/server/PacketHandler.scala @@ -36,6 +36,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) @@ -73,6 +74,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 { diff --git a/src/main/scala/li/cil/oc/server/component/DebugCard.scala b/src/main/scala/li/cil/oc/server/component/DebugCard.scala index da2e68985..6a7b3694d 100644 --- a/src/main/scala/li/cil/oc/server/component/DebugCard.scala +++ b/src/main/scala/li/cil/oc/server/component/DebugCard.scala @@ -35,6 +35,9 @@ import net.minecraft.world.WorldServer import net.minecraft.world.WorldSettings.GameType import net.minecraftforge.common.DimensionManager import net.minecraftforge.common.util.FakePlayerFactory +import net.minecraftforge.fluids.FluidRegistry +import net.minecraftforge.fluids.FluidStack +import net.minecraftforge.fluids.IFluidHandler class DebugCard(host: EnvironmentHost) extends prefab.ManagedEnvironment { override val node = Network.newNode(this, Visibility.Neighbors). @@ -426,6 +429,7 @@ object DebugCard { @Callback(doc = """function(x:number, y:number, z:number, slot:number[, count:number]):number - Reduce the size of an item stack in the inventory at the specified location.""") def removeItem(context: Context, args: Arguments): Array[AnyRef] = { + checkEnabled() val position = BlockPosition(args.checkDouble(0), args.checkDouble(1), args.checkDouble(2), world) InventoryUtils.inventoryAt(position) match { case Some(inventory) => @@ -438,6 +442,34 @@ object DebugCard { } } + @Callback(doc = """function(id:string, amount:number, x:number, y:number, z:number, side:number):boolean - Insert some fluid into the tank at the specified location.""") + def insertFluid(context: Context, args: Arguments): Array[AnyRef] = { + checkEnabled() + val fluid = FluidRegistry.getFluid(args.checkString(0)) + if (fluid == null) { + throw new IllegalArgumentException("invalid fluid id") + } + val amount = args.checkInteger(1) + val position = BlockPosition(args.checkDouble(2), args.checkDouble(3), args.checkDouble(4), world) + val side = args.checkSide(5, EnumFacing.values: _*) + world.getTileEntity(position) match { + case handler: IFluidHandler => result(handler.fill(side, new FluidStack(fluid, amount), true)) + case _ => result(null, "no tank") + } + } + + @Callback(doc = """function(amount:number, x:number, y:number, z:number, side:number):boolean - Remove some fluid from a tank at the specified location.""") + def removeFluid(context: Context, args: Arguments): Array[AnyRef] = { + checkEnabled() + val amount = args.checkInteger(0) + val position = BlockPosition(args.checkDouble(1), args.checkDouble(2), args.checkDouble(3), world) + val side = args.checkSide(4, EnumFacing.values: _*) + world.getTileEntity(position) match { + case handler: IFluidHandler => result(handler.drain(side, amount, true)) + case _ => result(null, "no tank") + } + } + // ----------------------------------------------------------------------- // override def load(nbt: NBTTagCompound) { diff --git a/src/main/scala/li/cil/oc/server/component/Geolyzer.scala b/src/main/scala/li/cil/oc/server/component/Geolyzer.scala index 07fde262f..4aa89311c 100644 --- a/src/main/scala/li/cil/oc/server/component/Geolyzer.scala +++ b/src/main/scala/li/cil/oc/server/component/Geolyzer.scala @@ -1,22 +1,27 @@ package li.cil.oc.server.component +import com.google.common.base.Strings import li.cil.oc.Settings import li.cil.oc.api import li.cil.oc.api.driver.EnvironmentHost import li.cil.oc.api.event.GeolyzerEvent import li.cil.oc.api.event.GeolyzerEvent.Analyze -import li.cil.oc.api.internal.Rotatable +import li.cil.oc.api.internal import li.cil.oc.api.machine.Arguments import li.cil.oc.api.machine.Callback import li.cil.oc.api.machine.Context +import li.cil.oc.api.network.Message import li.cil.oc.api.network.Visibility import li.cil.oc.api.prefab import li.cil.oc.util.BlockPosition import li.cil.oc.util.DatabaseAccess import li.cil.oc.util.ExtendedArguments._ import li.cil.oc.util.ExtendedWorld._ +import net.minecraft.block.Block +import net.minecraft.entity.player.EntityPlayer import net.minecraft.item.Item import net.minecraft.item.ItemStack +import net.minecraft.nbt.NBTTagCompound import net.minecraft.util.EnumFacing import net.minecraftforge.common.MinecraftForge @@ -51,7 +56,7 @@ class Geolyzer(val host: EnvironmentHost) extends prefab.ManagedEnvironment { def analyze(computer: Context, args: Arguments): Array[AnyRef] = if (Settings.get.allowItemStackInspection) { val side = args.checkSide(0, EnumFacing.values: _*) val globalSide = host match { - case rotatable: Rotatable => rotatable.toGlobal(side) + case rotatable: internal.Rotatable => rotatable.toGlobal(side) case _ => side } val options = args.optTable(1, Map.empty[AnyRef, AnyRef]) @@ -70,7 +75,7 @@ class Geolyzer(val host: EnvironmentHost) extends prefab.ManagedEnvironment { def store(computer: Context, args: Arguments): Array[AnyRef] = { val side = args.checkSide(0, EnumFacing.values: _*) val globalSide = host match { - case rotatable: Rotatable => rotatable.toGlobal(side) + case rotatable: internal.Rotatable => rotatable.toGlobal(side) case _ => side } @@ -93,4 +98,42 @@ class Geolyzer(val host: EnvironmentHost) extends prefab.ManagedEnvironment { }) } } + + override def onMessage(message: Message): Unit = { + super.onMessage(message) + if (message.name == "tablet.use") message.source.host match { + case machine: api.machine.Machine => (machine.host, message.data) match { + case (tablet: internal.Tablet, Array(nbt: NBTTagCompound, stack: ItemStack, player: EntityPlayer, blockPos: BlockPosition, side: EnumFacing, hitX: java.lang.Float, hitY: java.lang.Float, hitZ: java.lang.Float)) => + if (node.tryChangeBuffer(-Settings.get.geolyzerScanCost)) { + // TODO 1.5 replace with event (change event to allow arbitrary coordinates) + val world = player.getEntityWorld + val block = world.getBlock(blockPos) + + Block.blockRegistry.getNameForObject(block) match { + case name: String if !Strings.isNullOrEmpty(name) => nbt.setString("name", name) + case _ => + } + nbt.setInteger("metadata", block.getMetaFromState(world.getBlockMetadata(blockPos))) + nbt.setFloat("hardness", world.getBlockHardness(blockPos)) + nbt.setInteger("harvestLevel", world.getBlockHarvestLevel(blockPos)) + if (!Strings.isNullOrEmpty(world.getBlockHarvestTool(blockPos))) { + nbt.setString("harvestTool", world.getBlockHarvestTool(blockPos)) + } + nbt.setInteger("color", world.getBlockMapColor(blockPos).colorValue) + +// val event = new Analyze(host, Map.empty[AnyRef, AnyRef], side) +// MinecraftForge.EVENT_BUS.post(event) +// if (!event.isCanceled) { +// for ((key, value) <- event.data) value match { +// case number: java.lang.Number => nbt.setDouble(key, number.doubleValue()) +// case string: String if !string.isEmpty => nbt.setString(key, string) +// case _ => // Unsupported, ignore. +// } +// } + } + case _ => // Ignore. + } + case _ => // Ignore. + } + } } diff --git a/src/main/scala/li/cil/oc/server/component/UpgradeNavigation.scala b/src/main/scala/li/cil/oc/server/component/UpgradeNavigation.scala index aefdd926a..9100c4f7c 100644 --- a/src/main/scala/li/cil/oc/server/component/UpgradeNavigation.scala +++ b/src/main/scala/li/cil/oc/server/component/UpgradeNavigation.scala @@ -1,7 +1,9 @@ package li.cil.oc.server.component +import li.cil.oc.api import li.cil.oc.api.Network import li.cil.oc.api.driver.EnvironmentHost +import li.cil.oc.api.internal import li.cil.oc.api.internal.Rotatable import li.cil.oc.api.machine.Arguments import li.cil.oc.api.machine.Callback @@ -9,7 +11,11 @@ import li.cil.oc.api.machine.Context import li.cil.oc.api.network._ import li.cil.oc.api.prefab import li.cil.oc.common.item.data.NavigationUpgradeData +import li.cil.oc.util.BlockPosition +import net.minecraft.entity.player.EntityPlayer +import net.minecraft.item.ItemStack import net.minecraft.nbt.NBTTagCompound +import net.minecraft.util.EnumFacing class UpgradeNavigation(val host: EnvironmentHost with Rotatable) extends prefab.ManagedEnvironment { override val node = Network.newNode(this, Visibility.Network). @@ -43,6 +49,21 @@ class UpgradeNavigation(val host: EnvironmentHost with Rotatable) extends prefab result(size / 2) } + override def onMessage(message: Message): Unit = { + super.onMessage(message) + if (message.name == "tablet.use") message.source.host match { + case machine: api.machine.Machine => (machine.host, message.data) match { + case (tablet: internal.Tablet, Array(nbt: NBTTagCompound, stack: ItemStack, player: EntityPlayer, blockPos: BlockPosition, side: EnumFacing, hitX: java.lang.Float, hitY: java.lang.Float, hitZ: java.lang.Float)) => + val info = data.mapData(host.world) + nbt.setInteger("posX", blockPos.x - info.xCenter) + nbt.setInteger("posY", blockPos.y) + nbt.setInteger("posZ", blockPos.z - info.zCenter) + case _ => // Ignore. + } + case _ => // Ignore. + } + } + // ----------------------------------------------------------------------- // override def load(nbt: NBTTagCompound) { diff --git a/src/main/scala/li/cil/oc/server/component/traits/InventoryAnalytics.scala b/src/main/scala/li/cil/oc/server/component/traits/InventoryAnalytics.scala index 2bd5e1c25..ced66d1cc 100644 --- a/src/main/scala/li/cil/oc/server/component/traits/InventoryAnalytics.scala +++ b/src/main/scala/li/cil/oc/server/component/traits/InventoryAnalytics.scala @@ -7,7 +7,6 @@ import li.cil.oc.api.machine.Context import li.cil.oc.server.component.result import li.cil.oc.util.DatabaseAccess import li.cil.oc.util.ExtendedArguments._ -import net.minecraft.item.ItemStack trait InventoryAnalytics extends InventoryAware with NetworkAware { @Callback(doc = """function([slot:number]):table -- Get a description of the stack in the specified slot or the selected slot.""") @@ -21,12 +20,24 @@ trait InventoryAnalytics extends InventoryAware with NetworkAware { def storeInternal(context: Context, args: Arguments): Array[AnyRef] = { val localSlot = args.checkSlot(inventory, 0) val dbAddress = args.checkString(1) - def store(stack: ItemStack) = DatabaseAccess.withDatabase(node, dbAddress, database => { + val localStack = inventory.getStackInSlot(localSlot) + DatabaseAccess.withDatabase(node, dbAddress, database => { val dbSlot = args.checkSlot(database.data, 2) val nonEmpty = database.data.getStackInSlot(dbSlot) != null - database.data.setInventorySlotContents(dbSlot, stack.copy()) + database.data.setInventorySlotContents(dbSlot, localStack.copy()) result(nonEmpty) }) - store(inventory.getStackInSlot(localSlot)) + } + + @Callback(doc = """function(slot:number, dbAddress:string, dbSlot:number):boolean -- Compare an item in the specified slot with one in the database with the specified address.""") + def compareToDatabase(context: Context, args: Arguments): Array[AnyRef] = { + val localSlot = args.checkSlot(inventory, 0) + val dbAddress = args.checkString(1) + val localStack = inventory.getStackInSlot(localSlot) + DatabaseAccess.withDatabase(node, dbAddress, database => { + val dbSlot = args.checkSlot(database.data, 2) + val dbStack = database.data.getStackInSlot(dbSlot) + result(haveSameItemType(localStack, dbStack)) + }) } } diff --git a/src/main/scala/li/cil/oc/server/component/traits/InventoryAware.scala b/src/main/scala/li/cil/oc/server/component/traits/InventoryAware.scala index 2342fb1d7..5bc635ea2 100644 --- a/src/main/scala/li/cil/oc/server/component/traits/InventoryAware.scala +++ b/src/main/scala/li/cil/oc/server/component/traits/InventoryAware.scala @@ -23,6 +23,7 @@ trait InventoryAware { protected def stackInSlot(slot: Int) = Option(inventory.getStackInSlot(slot)) protected def haveSameItemType(stackA: ItemStack, stackB: ItemStack) = - stackA.getItem == stackB.getItem && + stackA != null && stackB != null && + stackA.getItem == stackB.getItem && (!stackA.getHasSubtypes || stackA.getItemDamage == stackB.getItemDamage) } diff --git a/src/main/scala/li/cil/oc/server/machine/Machine.scala b/src/main/scala/li/cil/oc/server/machine/Machine.scala index 5891e8e8e..92dfbefe8 100644 --- a/src/main/scala/li/cil/oc/server/machine/Machine.scala +++ b/src/main/scala/li/cil/oc/server/machine/Machine.scala @@ -238,6 +238,7 @@ class Machine(val host: MachineHost) extends prefab.ManagedEnvironment with mach case arg: java.lang.String => arg case arg: Array[Byte] => arg case arg: Map[_, _] if arg.isEmpty || arg.head._1.isInstanceOf[String] && arg.head._2.isInstanceOf[String] => arg + case arg: NBTTagCompound => arg case arg => OpenComputers.log.warn("Trying to push signal with an unsupported argument of type " + arg.getClass.getName) null @@ -247,7 +248,7 @@ class Machine(val host: MachineHost) extends prefab.ManagedEnvironment with mach } }) - override def popSignal(): Machine.Signal = signals.synchronized(if (signals.isEmpty) null else signals.dequeue()) + override def popSignal(): Machine.Signal = signals.synchronized(if (signals.isEmpty) null else signals.dequeue().convert()) override def methods(value: scala.AnyRef) = Callbacks(value).map(entry => { val (name, callback) = entry @@ -628,6 +629,7 @@ class Machine(val host: MachineHost) extends prefab.ManagedEnvironment with mach data += tag.getStringTagAt(i) -> tag.getStringTagAt(i + 1) } data + case tag: NBTTagCompound => tag case _ => null }.toArray[AnyRef]) }) @@ -705,6 +707,7 @@ class Machine(val host: MachineHost) extends prefab.ManagedEnvironment with mach list.append(value.toString) } args.setTag("arg" + i, list) + case (arg: NBTTagCompound, i) => args.setTag("arg" + i, arg) case (_, i) => args.setByte("arg" + i, -1) } }) @@ -936,7 +939,9 @@ object Machine extends MachineAPI { } /** Signals are messages sent to the Lua state from Java asynchronously. */ - private[machine] class Signal(val name: String, val args: Array[AnyRef]) extends machine.Signal + private[machine] class Signal(val name: String, val args: Array[AnyRef]) extends machine.Signal { + def convert() = new Signal(name, Registry.convert(args)) + } private val threadPool = ThreadPoolFactory.create("Computer", Settings.get.threads) } \ No newline at end of file