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. -- Start of non-standard stuff.
address = os.address, address = os.address,
isRobot = os.isRobot,
freeMemory = os.freeMemory, freeMemory = os.freeMemory,
totalMemory = os.totalMemory, totalMemory = os.totalMemory,
uptime = os.uptime, 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 allowActivateBlocks = true
var canAttackPlayers = false var canAttackPlayers = false
var canPlaceInAir = false
var itemDamageRate = 0.05 var itemDamageRate = 0.05
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
@ -386,6 +387,16 @@ object Config {
"players in the game."). "players in the game.").
getBoolean(canAttackPlayers) 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, "" + itemDamageRate = config.get("robot", "itemDamageRate", itemDamageRate, "" +
"The rate at which items used as tools by robots take damage. A value\n" + "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" + "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 lastError = message
def isRobot = false
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
def isRunning = state.synchronized(state.top != Computer.State.Stopped) 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] = def isRunning(context: Context, args: Arguments): Array[AnyRef] =
Array(Boolean.box(isRunning)) 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() { override def update() {
@ -734,27 +732,6 @@ class Computer(val owner: tileentity.Computer) extends ManagedComponent with Con
}) })
lua.setField(-2, "uptime") 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. // Allow the computer to figure out its own id in the component network.
lua.pushScalaFunction(lua => { lua.pushScalaFunction(lua => {
Option(node.address) match { Option(node.address) match {
@ -765,6 +742,34 @@ class Computer(val owner: tileentity.Computer) extends ManagedComponent with Con
}) })
lua.setField(-2, "address") 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. // And it's ROM address.
lua.pushScalaFunction(lua => { lua.pushScalaFunction(lua => {
rom.foreach(rom => Option(rom.node.address) match { rom.foreach(rom => Option(rom.node.address) match {

View File

@ -1,5 +1,6 @@
package li.cil.oc.server.component package li.cil.oc.server.component
import li.cil.oc.Config
import li.cil.oc.api.network.{LuaCallback, Arguments, Context} import li.cil.oc.api.network.{LuaCallback, Arguments, Context}
import li.cil.oc.common.tileentity import li.cil.oc.common.tileentity
import li.cil.oc.server.{PacketSender => ServerPacketSender} 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.block.{BlockFluid, Block}
import net.minecraft.entity.item.EntityItem import net.minecraft.entity.item.EntityItem
import net.minecraft.item.{ItemStack, ItemBlock} 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.common.ForgeDirection
import net.minecraftforge.fluids.FluidRegistry import net.minecraftforge.fluids.FluidRegistry
import scala.Some 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] = override def isRobot = true
Array(java.lang.Boolean.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") @LuaCallback("compare")
def compare(context: Context, args: Arguments): Array[AnyRef] = { def compare(context: Context, args: Arguments): Array[AnyRef] = {
val side = checkSideForAction(args, 0) val side = checkSideForAction(args, 0)
@ -174,6 +121,65 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) {
result(false) 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") @LuaCallback("detect")
def detect(context: Context, args: Arguments): Array[AnyRef] = { def detect(context: Context, args: Arguments): Array[AnyRef] = {
val side = checkSideForAction(args, 0) val side = checkSideForAction(args, 0)
@ -207,11 +213,9 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) {
if (side.getOpposite == facing) { if (side.getOpposite == facing) {
throw new IllegalArgumentException("invalid side") throw new IllegalArgumentException("invalid side")
} }
val sneaky = args.isBoolean(2) && args.checkBoolean(2)
val player = robot.player(facing) val player = robot.player(facing)
Option(simulateClick(facing, side, 0.49)) match { Option(pick(facing, side, 0.49)) match {
case Some(hit) => case Some(hit) =>
player.setSneaking(sneaky)
val what = hit.typeOfHit match { val what = hit.typeOfHit match {
case EnumMovingObjectType.ENTITY => case EnumMovingObjectType.ENTITY =>
player.attackTargetEntityWithCurrentItem(hit.entityHit) 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) val broke = player.clickBlock(hit.blockX, hit.blockY, hit.blockZ, hit.sideHit)
result(broke, "block") result(broke, "block")
} }
player.setSneaking(false)
what what
case _ => case _ =>
player.closestLivingEntity(facing) match { 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 sneaky = args.isBoolean(2) && args.checkBoolean(2)
val player = robot.player(facing) val player = robot.player(facing)
Option(simulateClick(facing, side, 0.51)) match { def activationResult(activationType: ActivationType.Value): Array[AnyRef] =
case Some(hit) => activationType match {
player.setSneaking(sneaky)
val what = 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.BlockActivated => result(true, "block_activated")
case ActivationType.ItemPlaced => result(true, "item_placed") case ActivationType.ItemPlaced => result(true, "item_placed")
case ActivationType.ItemUsed => result(true, "item_used") case ActivationType.ItemUsed => result(true, "item_used")
case _ => result(false) case _ => result(false)
} }
player.setSneaking(sneaky)
val what = Option(pick(facing, side, 0.51)) match {
case Some(hit) =>
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 (bx, by, bz, hx, hy, hz) = clickParamsFromHit(hit)
activationResult(player.activateBlockOrUseItem(bx, by, bz, hit.sideHit, hx, hy, hz))
}
case _ =>
(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) player.setSneaking(false)
what what
case _ =>
if (player.useEquippedItem()) result(true, "item_used")
else result(false)
}
} }
@LuaCallback("durability") @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 (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 (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) 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 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) = 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 || { event.useBlock == Event.Result.DENY || {
val direction = ForgeDirection.getOrientation(side).getOpposite val result = stack.tryPlaceItemIntoWorld(this, world, x, y, z, side, hitX, hitY, hitZ)
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)
if (stack.stackSize <= 0) ForgeEventFactory.onPlayerDestroyItem(this, stack) if (stack.stackSize <= 0) ForgeEventFactory.onPlayerDestroyItem(this, stack)
result result
} }