added setting to emulate CC turtle placing behavior (allow placing blocks in thin air, without any reference point - and no, the bot itself doesn't count!); properly ensuring placement is only possible on existing blocks for robot.place() (if the setting is false); robot Lua library wrapping the low level callbacks a bit

This commit is contained in:
Florian Nücke 2013-11-19 03:29:57 +01:00
parent 358328deb1
commit ba1f66e989
6 changed files with 306 additions and 106 deletions

View File

@ -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,

View File

@ -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

View File

@ -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" +

View File

@ -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 {

View File

@ -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) =

View File

@ -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
}