diff --git a/assets/opencomputers/lua/kernel.lua b/assets/opencomputers/lua/kernel.lua index bb3ecd535..f486db3b3 100644 --- a/assets/opencomputers/lua/kernel.lua +++ b/assets/opencomputers/lua/kernel.lua @@ -234,6 +234,7 @@ sandbox = { -- Start of non-standard stuff. address = os.address, + isRobot = os.isRobot, freeMemory = os.freeMemory, totalMemory = os.totalMemory, uptime = os.uptime, diff --git a/assets/opencomputers/lua/rom/lib/robot.lua b/assets/opencomputers/lua/rom/lib/robot.lua new file mode 100644 index 000000000..74766aa2b --- /dev/null +++ b/assets/opencomputers/lua/rom/lib/robot.lua @@ -0,0 +1,160 @@ +if not os.isRobot() then + return +end +local function proxy() + return component.computer +end +local robot = {} + + +function robot.select(index) + return proxy().select(index) +end + +function robot.count() + return proxy().count() +end + +function robot.space() + return proxy().select() +end + +function robot.compareTo(index) + return proxy().compareTo(index) +end + +function robot.transferTo(index, count) + return proxy().transferTo(index, count) +end + +function robot.compare() + return proxy().compare(sides.front) +end + +function robot.compareUp() + return proxy().compare(sides.up) +end + +function robot.compareDown() + return proxy().compare(sides.down) +end + +function robot.drop(count) + checkArg(1, count, "nil", "number") + return proxy().drop(sides.front, count) +end + +function robot.dropUp(count) + checkArg(1, count, "nil", "number") + return proxy().drop(sides.up, count) +end + +function robot.dropDown(count) + checkArg(1, count, "nil", "number") + return proxy().drop(sides.down, count) +end + +function robot.place(side, sneaky) + checkArg(1, side, "nil", "number") + return proxy().place(sides.front, side, sneaky ~= nil and sneaky ~= false) +end + +function robot.placeUp(side, sneaky) + checkArg(1, side, "nil", "number") + return proxy().place(sides.up, side, sneaky ~= nil and sneaky ~= false) +end + +function robot.placeDown(side, sneaky) + checkArg(1, side, "nil", "number") + return proxy().place(sides.down, side, sneaky ~= nil and sneaky ~= false) +end + +function robot.suck(count) + checkArg(1, count, "nil", "number") + return proxy().suck(sides.front, count) +end + +function robot.suckUp(count) + checkArg(1, count, "nil", "number") + return proxy().suck(sides.up, count) +end + +function robot.suckDown(count) + checkArg(1, count, "nil", "number") + return proxy().suck(sides.down, count) +end + + +function robot.detect() + return proxy().detect(sides.front) +end + +function robot.detectUp() + return proxy().detect(sides.up) +end + +function robot.detectDown() + return proxy().detect(sides.down) +end + + +function robot.swing(side) + checkArg(1, side, "nil", "number") + return proxy().swing(sides.front, side) +end + +function robot.swingUp(side) + checkArg(1, side, "nil", "number") + return proxy().swing(sides.up, side) +end + +function robot.swingDown(side) + checkArg(1, side, "nil", "number") + return proxy().swing(sides.down, side) +end + +function robot.use(side, sneaky) + checkArg(1, side, "nil", "number") + return proxy().use(sides.front, side, sneaky ~= nil and sneaky ~= false) +end + +function robot.useUp(side, sneaky) + checkArg(1, side, "nil", "number") + return proxy().use(sides.up, side, sneaky ~= nil and sneaky ~= false) +end + +function robot.useDown(side, sneaky) + checkArg(1, side, "nil", "number") + return proxy().use(sides.down, side, sneaky ~= nil and sneaky ~= false) +end + +function robot.durability() + return proxy().durability() +end + + +function robot.forward() + return proxy().move(sides.front) +end + +function robot.back() + return proxy().move(sides.back) +end + +function robot.up() + return proxy().move(sides.up) +end + +function robot.down() + return proxy().move(sides.down) +end + +function robot.turnLeft() + return proxy().turn(false) +end + +function robot.turnRight() + return proxy().turn(true) +end + +_G.robot = robot \ No newline at end of file diff --git a/li/cil/oc/Config.scala b/li/cil/oc/Config.scala index c7c698978..8ac5f5c8e 100644 --- a/li/cil/oc/Config.scala +++ b/li/cil/oc/Config.scala @@ -82,6 +82,7 @@ object Config { var allowActivateBlocks = true var canAttackPlayers = false + var canPlaceInAir = false var itemDamageRate = 0.05 // ----------------------------------------------------------------------- // @@ -386,6 +387,16 @@ object Config { "players in the game."). getBoolean(canAttackPlayers) + canPlaceInAir = config.get("robot", "canPlaceInAir", canPlaceInAir, "" + + "Whether robots may place blocks in thin air, i.e. without a reference\n" + + "point (as is required for real players). Set this to true to emulate\n" + + "ComputerCraft's Turtles' behavior. When left false robots have to\n" + + "target an existing block face to place another block. For example,\n" + + "if the robots stands on a perfect plain, you have to call\n" + + "`robot.place(sides.down)` to place a block, instead of just\n" + + "`robot.place()`, which will default to `robot.place(sides.front)`."). + getBoolean(canPlaceInAir) + itemDamageRate = config.get("robot", "itemDamageRate", itemDamageRate, "" + "The rate at which items used as tools by robots take damage. A value\n" + "of one means that items lose durability as quickly as when they are\n" + diff --git a/li/cil/oc/server/component/Computer.scala b/li/cil/oc/server/component/Computer.scala index fb2aef544..d99eb9595 100644 --- a/li/cil/oc/server/component/Computer.scala +++ b/li/cil/oc/server/component/Computer.scala @@ -81,6 +81,8 @@ class Computer(val owner: tileentity.Computer) extends ManagedComponent with Con def lastError = message + def isRobot = false + // ----------------------------------------------------------------------- // def isRunning = state.synchronized(state.top != Computer.State.Stopped) @@ -168,10 +170,6 @@ class Computer(val owner: tileentity.Computer) extends ManagedComponent with Con def isRunning(context: Context, args: Arguments): Array[AnyRef] = Array(Boolean.box(isRunning)) - @LuaCallback(value = "isRobot", direct = true) - def isRobot(context: Context, args: Arguments): Array[AnyRef] = - Array(java.lang.Boolean.FALSE) - // ----------------------------------------------------------------------- // override def update() { @@ -734,27 +732,6 @@ class Computer(val owner: tileentity.Computer) extends ManagedComponent with Con }) lua.setField(-2, "uptime") - // Allow the system to read how much memory it uses and has available. - lua.pushScalaFunction(lua => { - lua.pushInteger(lua.getTotalMemory - kernelMemory) - 1 - }) - lua.setField(-2, "totalMemory") - - lua.pushScalaFunction(lua => { - // This is *very* unlikely, but still: avoid this getting larger than - // what we report as the total memory. - lua.pushInteger(lua.getFreeMemory min (lua.getTotalMemory - kernelMemory)) - 1 - }) - lua.setField(-2, "freeMemory") - - lua.pushScalaFunction(lua => { - lua.pushBoolean(signal(lua.checkString(1), parseArguments(lua, 2): _*)) - 1 - }) - lua.setField(-2, "pushSignal") - // Allow the computer to figure out its own id in the component network. lua.pushScalaFunction(lua => { Option(node.address) match { @@ -765,6 +742,34 @@ class Computer(val owner: tileentity.Computer) extends ManagedComponent with Con }) lua.setField(-2, "address") + // Are we a robot? (No this is not a CAPTCHA.) + lua.pushScalaFunction(lua => { + lua.pushBoolean(isRobot) + 1 + }) + lua.setField(-2, "isRobot") + + lua.pushScalaFunction(lua => { + // This is *very* unlikely, but still: avoid this getting larger than + // what we report as the total memory. + lua.pushInteger(lua.getFreeMemory min (lua.getTotalMemory - kernelMemory)) + 1 + }) + lua.setField(-2, "freeMemory") + + // Allow the system to read how much memory it uses and has available. + lua.pushScalaFunction(lua => { + lua.pushInteger(lua.getTotalMemory - kernelMemory) + 1 + }) + lua.setField(-2, "totalMemory") + + lua.pushScalaFunction(lua => { + lua.pushBoolean(signal(lua.checkString(1), parseArguments(lua, 2): _*)) + 1 + }) + lua.setField(-2, "pushSignal") + // And it's ROM address. lua.pushScalaFunction(lua => { rom.foreach(rom => Option(rom.node.address) match { diff --git a/li/cil/oc/server/component/Robot.scala b/li/cil/oc/server/component/Robot.scala index d8fa0e031..f7d760a91 100644 --- a/li/cil/oc/server/component/Robot.scala +++ b/li/cil/oc/server/component/Robot.scala @@ -1,5 +1,6 @@ package li.cil.oc.server.component +import li.cil.oc.Config import li.cil.oc.api.network.{LuaCallback, Arguments, Context} import li.cil.oc.common.tileentity import li.cil.oc.server.{PacketSender => ServerPacketSender} @@ -7,7 +8,7 @@ import li.cil.oc.util.ActivationType import net.minecraft.block.{BlockFluid, Block} import net.minecraft.entity.item.EntityItem import net.minecraft.item.{ItemStack, ItemBlock} -import net.minecraft.util.{EnumMovingObjectType, Vec3} +import net.minecraft.util.{MovingObjectPosition, EnumMovingObjectType, Vec3} import net.minecraftforge.common.ForgeDirection import net.minecraftforge.fluids.FluidRegistry import scala.Some @@ -28,8 +29,7 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) { // ----------------------------------------------------------------------- // - override def isRobot(context: Context, args: Arguments): Array[AnyRef] = - Array(java.lang.Boolean.TRUE) + override def isRobot = true // ----------------------------------------------------------------------- // @@ -104,59 +104,6 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) { }) } - @LuaCallback("drop") - def drop(context: Context, args: Arguments): Array[AnyRef] = { - val side = checkSideForAction(args, 0) - val count = checkOptionalItemCount(args, 1) - // TODO inventory, if available - - result(robot.dropSlot(actualSlot(selectedSlot), count, side)) - // This also works, but throws the items a little too far. - // result(robot.player().dropPlayerItemWithRandomChoice(robot.decrStackSize(actualSlot(selectedSlot), count), false) != null) - } - - @LuaCallback("place") - def place(context: Context, args: Arguments): Array[AnyRef] = { - val lookSide = checkSideForAction(args, 0) - val side = if (args.isInteger(1)) checkSide(args, 1) else lookSide - if (side.getOpposite == lookSide) { - throw new IllegalArgumentException("invalid side") - } - val sneaky = args.isBoolean(2) && args.checkBoolean(2) - val player = robot.player(lookSide) - val stack = player.robotInventory.selectedItemStack - if (stack == null || stack.stackSize == 0) { - result(false) - } - else { - player.setSneaking(sneaky) - val (bx, by, bz) = (x + lookSide.offsetX, y + lookSide.offsetY, z + lookSide.offsetZ) - val (hx, hy, hz) = (0.5f + side.offsetX * 0.5f, 0.5f + side.offsetY * 0.5f, 0.5f + side.offsetZ * 0.5f) - val ok = player.placeBlock(player.robotInventory.selectedItemStack, bx, by, bz, side.getOpposite.ordinal, hx, hy, hz) - player.setSneaking(false) - if (stack.stackSize <= 0) { - robot.setInventorySlotContents(player.robotInventory.selectedSlot, null) - } - result(ok) - } - } - - @LuaCallback("suck") - def suck(context: Context, args: Arguments): Array[AnyRef] = { - val side = checkSideForAction(args, 0) - val count = checkOptionalItemCount(args, 1) - // TODO inventory, if available - for (entity <- robot.player().entitiesOnSide[EntityItem](side) if !entity.isDead && entity.delayBeforeCanPickup <= 0) { - val stack = entity.getEntityItem - val size = stack.stackSize - entity.onCollideWithPlayer(robot.player()) - if (stack.stackSize < size || entity.isDead) return result(true) - } - result(false) - } - - // ----------------------------------------------------------------------- // - @LuaCallback("compare") def compare(context: Context, args: Arguments): Array[AnyRef] = { val side = checkSideForAction(args, 0) @@ -174,6 +121,65 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) { result(false) } + @LuaCallback("drop") + def drop(context: Context, args: Arguments): Array[AnyRef] = { + val facing = checkSideForAction(args, 0) + val count = checkOptionalItemCount(args, 1) + // TODO inventory, if available + + result(robot.dropSlot(actualSlot(selectedSlot), count, facing)) + // This also works, but throws the items a little too far. + // result(robot.player().dropPlayerItemWithRandomChoice(robot.decrStackSize(actualSlot(selectedSlot), count), false) != null) + } + + @LuaCallback("place") + def place(context: Context, args: Arguments): Array[AnyRef] = { + val facing = checkSideForAction(args, 0) + val side = if (args.isInteger(1)) checkSide(args, 1) else facing + if (side.getOpposite == facing) { + throw new IllegalArgumentException("invalid side") + } + val sneaky = args.isBoolean(2) && args.checkBoolean(2) + val player = robot.player(facing) + val stack = player.robotInventory.selectedItemStack + if (stack == null || stack.stackSize == 0) { + result(false, "nothing selected") + } + else { + player.setSneaking(sneaky) + val what = Option(pick(facing, side, 0.51)) match { + case Some(hit) if hit.typeOfHit == EnumMovingObjectType.TILE => + val (bx, by, bz, hx, hy, hz) = clickParamsFromHit(hit) + player.placeBlock(stack, bx, by, bz, hit.sideHit, hx, hy, hz) + case None if Config.canPlaceInAir && player.closestLivingEntity(facing).isEmpty => + val (bx, by, bz, hx, hy, hz) = clickParamsFromFacing(facing, side) + player.placeBlock(stack, bx, by, bz, side.getOpposite.ordinal, hx, hy, hz) + case _ => false + } + player.setSneaking(false) + if (stack.stackSize <= 0) { + robot.setInventorySlotContents(player.robotInventory.selectedSlot, null) + } + result(what) + } + } + + @LuaCallback("suck") + def suck(context: Context, args: Arguments): Array[AnyRef] = { + val facing = checkSideForAction(args, 0) + val count = checkOptionalItemCount(args, 1) + // TODO inventory, if available + for (entity <- robot.player().entitiesOnSide[EntityItem](facing) if !entity.isDead && entity.delayBeforeCanPickup <= 0) { + val stack = entity.getEntityItem + val size = stack.stackSize + entity.onCollideWithPlayer(robot.player()) + if (stack.stackSize < size || entity.isDead) return result(true) + } + result(false) + } + + // ----------------------------------------------------------------------- // + @LuaCallback("detect") def detect(context: Context, args: Arguments): Array[AnyRef] = { val side = checkSideForAction(args, 0) @@ -207,11 +213,9 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) { if (side.getOpposite == facing) { throw new IllegalArgumentException("invalid side") } - val sneaky = args.isBoolean(2) && args.checkBoolean(2) val player = robot.player(facing) - Option(simulateClick(facing, side, 0.49)) match { + Option(pick(facing, side, 0.49)) match { case Some(hit) => - player.setSneaking(sneaky) val what = hit.typeOfHit match { case EnumMovingObjectType.ENTITY => player.attackTargetEntityWithCurrentItem(hit.entityHit) @@ -220,7 +224,6 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) { val broke = player.clickBlock(hit.blockX, hit.blockY, hit.blockZ, hit.sideHit) result(broke, "block") } - player.setSneaking(false) what case _ => player.closestLivingEntity(facing) match { @@ -242,31 +245,37 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) { } val sneaky = args.isBoolean(2) && args.checkBoolean(2) val player = robot.player(facing) - Option(simulateClick(facing, side, 0.51)) match { + def activationResult(activationType: ActivationType.Value): Array[AnyRef] = + activationType match { + case ActivationType.BlockActivated => result(true, "block_activated") + case ActivationType.ItemPlaced => result(true, "item_placed") + case ActivationType.ItemUsed => result(true, "item_used") + case _ => result(false) + } + player.setSneaking(sneaky) + val what = Option(pick(facing, side, 0.51)) match { case Some(hit) => - player.setSneaking(sneaky) - val what = hit.typeOfHit match { + hit.typeOfHit match { case EnumMovingObjectType.ENTITY => // TODO Is there any practical use for this? Most of the stuff related to this is still 'obfuscated'... result(false, "entity") case EnumMovingObjectType.TILE => - val (hx, hy, hz) = ( - (hit.hitVec.xCoord - hit.blockX).toFloat, - (hit.hitVec.yCoord - hit.blockY).toFloat, - (hit.hitVec.zCoord - hit.blockZ).toFloat) - player.activateBlockOrUseItem(hit.blockX, hit.blockY, hit.blockZ, hit.sideHit, hx, hy, hz) match { - case ActivationType.BlockActivated => result(true, "block_activated") - case ActivationType.ItemPlaced => result(true, "item_placed") - case ActivationType.ItemUsed => result(true, "item_used") - case _ => result(false) - } + val (bx, by, bz, hx, hy, hz) = clickParamsFromHit(hit) + activationResult(player.activateBlockOrUseItem(bx, by, bz, hit.sideHit, hx, hy, hz)) } - player.setSneaking(false) - what case _ => - if (player.useEquippedItem()) result(true, "item_used") - else result(false) + (if (Config.canPlaceInAir) { + val (bx, by, bz, hx, hy, hz) = clickParamsFromFacing(facing, side) + player.activateBlockOrUseItem(bx, by, bz, side.getOpposite.ordinal, hx, hy, hz) + } else ActivationType.None) match { + case ActivationType.None => + if (player.useEquippedItem()) result(true, "item_used") + else result(false) + case activationType => activationResult(activationType) + } } + player.setSneaking(false) + what } @LuaCallback("durability") @@ -281,7 +290,7 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) { } } - def simulateClick(facing: ForgeDirection, side: ForgeDirection, range: Double) = { + def pick(facing: ForgeDirection, side: ForgeDirection, range: Double) = { val (bx, by, bz) = (x + facing.offsetX, y + facing.offsetY, z + facing.offsetZ) val (hx, hy, hz) = (0.5 + side.offsetX * range, 0.5 + side.offsetY * range, 0.5 + side.offsetZ * range) val origin = Vec3.createVectorHelper(x + 0.5, y + 0.5, z + 0.5) @@ -314,6 +323,22 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) { private def stackInSlot(slot: Int) = Option(robot.getStackInSlot(actualSlot(slot))) + private def clickParamsFromHit(hit: MovingObjectPosition) = { + (hit.blockX, hit.blockY, hit.blockZ, + (hit.hitVec.xCoord - hit.blockX).toFloat, + (hit.hitVec.yCoord - hit.blockY).toFloat, + (hit.hitVec.zCoord - hit.blockZ).toFloat) + } + + private def clickParamsFromFacing(facing: ForgeDirection, side: ForgeDirection) = { + (x + facing.offsetX + side.offsetX, + y + facing.offsetY + side.offsetY, + z + facing.offsetZ + side.offsetZ, + 0.5f - side.offsetX * 0.5f, + 0.5f - side.offsetY * 0.5f, + 0.5f - side.offsetZ * 0.5f) + } + // ----------------------------------------------------------------------- // private def checkOptionalItemCount(args: Arguments, n: Int) = diff --git a/li/cil/oc/util/RobotPlayer.scala b/li/cil/oc/util/RobotPlayer.scala index fc1b28700..77d914ac9 100644 --- a/li/cil/oc/util/RobotPlayer.scala +++ b/li/cil/oc/util/RobotPlayer.scala @@ -160,9 +160,7 @@ class RobotPlayer(val robot: Robot) extends FakePlayer(robot.world, "OpenCompute } event.useBlock == Event.Result.DENY || { - val direction = ForgeDirection.getOrientation(side).getOpposite - val (onX, onY, onZ) = (x + direction.offsetX, y + direction.offsetY, z + direction.offsetZ) - val result = stack.tryPlaceItemIntoWorld(this, world, onX, onY, onZ, side, hitX, hitY, hitZ) + val result = stack.tryPlaceItemIntoWorld(this, world, x, y, z, side, hitX, hitY, hitZ) if (stack.stackSize <= 0) ForgeEventFactory.onPlayerDestroyItem(this, stack) result }