get a move on. robots can now move. there's no animation yet and accordingly collision bounds aren't interpolated either, but movement basically works. in a very... simple manner, I must say (re-uses existing tile entity, on both server and client); cleaned up delegate class a bit (the node.remove in block break should be redundant since we do this in the environment tile entity base class)

This commit is contained in:
Florian Nücke 2013-11-19 21:45:11 +01:00
parent 827344b543
commit 3324371ec6
10 changed files with 221 additions and 101 deletions

View File

@ -85,6 +85,18 @@ object Config {
var canPlaceInAir = false
var itemDamageRate = 0.05
// ----------------------------------------------------------------------- //
// robot.delays
var dropDelay = 0.1
var moveDelay = 0.4
var placeDelay = 0.3
var suckDelay = 0.1
var swingDelay = 0.4
var turnDelay = 0.4
var useDelay = 0.4
// ----------------------------------------------------------------------- //
def load(file: File) = {

View File

@ -27,6 +27,7 @@ class PacketHandler extends CommonPacketHandler {
case PacketType.ComputerStateResponse => onComputerStateResponse(p)
case PacketType.PowerStateResponse => onPowerStateResponse(p)
case PacketType.RedstoneStateResponse => onRedstoneStateResponse(p)
case PacketType.RobotMove => onRobotMove(p)
case PacketType.RobotSelectedSlotResponse => onRobotSelectedSlotResponse(p)
case PacketType.RotatableStateResponse => onRotatableStateResponse(p)
case PacketType.ScreenBufferResponse => onScreenBufferResponse(p)
@ -78,6 +79,12 @@ class PacketHandler extends CommonPacketHandler {
case _ => // Invalid packet.
}
def onRobotMove(p: PacketParser) =
p.readTileEntity[Robot]() match {
case Some(t) => t.move(p.readInt(), p.readInt(), p.readInt())
case _ => // Invalid packet.
}
def onRobotSelectedSlotResponse(p: PacketParser) =
p.readTileEntity[Robot]() match {
case Some(t) => t.selectedSlot = p.readInt()

View File

@ -10,6 +10,7 @@ object PacketType extends Enumeration {
val RedstoneStateRequest = Value("RedstoneStateRequest")
val RedstoneStateResponse = Value("RedstoneStateResponse")
val RobotMove = Value("RobotMove")
val RobotSelectedSlotRequest = Value("RobotSelectedSlotRequest")
val RobotSelectedSlotResponse = Value("RobotSelectedSlotResponse")

View File

@ -1,7 +1,6 @@
package li.cil.oc.common.block
import li.cil.oc.common.tileentity
import net.minecraft.block.Block
import net.minecraft.client.renderer.texture.IconRegister
import net.minecraft.entity.EntityLivingBase
import net.minecraft.entity.player.EntityPlayer

View File

@ -1,7 +1,6 @@
package li.cil.oc.common.block
import java.util
import li.cil.oc.api.network.Environment
import li.cil.oc.common.tileentity.Rotatable
import li.cil.oc.{Config, CreativeTab}
import net.minecraft.block.Block
@ -95,14 +94,7 @@ class Delegator[Child <: Delegate](id: Int, name: String) extends Block(id, Mate
override def breakBlock(world: World, x: Int, y: Int, z: Int, blockId: Int, metadata: Int) = {
subBlock(metadata) match {
case Some(subBlock) => {
world.getBlockTileEntity(x, y, z) match {
case environment: Environment if environment.node != null =>
environment.node.remove()
case _ => // Nothing special to do.
}
subBlock.breakBlock(world, x, y, z, blockId)
}
case Some(subBlock) => subBlock.breakBlock(world, x, y, z, blockId)
case _ => // Invalid but avoid match error.
}
super.breakBlock(world, x, y, z, blockId, metadata)

View File

@ -10,7 +10,16 @@ import net.minecraftforge.common.ForgeDirection
class Robot(val parent: SpecialDelegator) extends Computer with SpecialDelegate {
val unlocalizedName = "Robot"
override def createTileEntity(world: World) = Some(new tileentity.Robot(world.isRemote))
var moving = new ThreadLocal[Option[tileentity.Robot]] {
override protected def initialValue = None
}
override def createTileEntity(world: World) = {
moving.get match {
case Some(robot) => Some(robot)
case _ => Some(new tileentity.Robot(world.isRemote))
}
}
// ----------------------------------------------------------------------- //
@ -47,4 +56,10 @@ class Robot(val parent: SpecialDelegator) extends Computer with SpecialDelegate
}
else false
}
override def onBlockPreDestroy(world: World, x: Int, y: Int, z: Int) {
if (moving.get.isEmpty) {
super.onBlockPreDestroy(world, x, y, z)
}
}
}

View File

@ -1,22 +1,21 @@
package li.cil.oc.common.tileentity
import cpw.mods.fml.relauncher.{SideOnly, Side}
import li.cil.oc.Config
import li.cil.oc.api
import li.cil.oc.api.driver.Slot
import li.cil.oc.api.network._
import li.cil.oc.client.{PacketSender => ClientPacketSender}
import li.cil.oc.common
import li.cil.oc.server.component
import li.cil.oc.server.component.GraphicsCard
import li.cil.oc.server.component.robot.Player
import li.cil.oc.server.driver.Registry
import li.cil.oc.server.{PacketSender => ServerPacketSender, component}
import li.cil.oc.util.ExtendedNBT._
import li.cil.oc.{Blocks, Config, api, common}
import net.minecraft.client.Minecraft
import net.minecraft.item.ItemStack
import net.minecraft.nbt.NBTTagCompound
import net.minecraftforge.common.ForgeDirection
import scala.Some
import scala.collection.convert.WrapAsScala._
class Robot(isRemote: Boolean) extends Computer(isRemote) with Buffer with PowerInformation {
def this() = this(false)
@ -42,43 +41,83 @@ class Robot(isRemote: Boolean) extends Computer(isRemote) with Buffer with Power
}
else (null, null, null, null)
var selectedSlot = 0
private lazy val player_ = new Player(this)
// ----------------------------------------------------------------------- //
def player(facing: ForgeDirection = facing, side: ForgeDirection = facing) = {
assert(isServer)
player_.updatePositionAndRotation(facing, side)
player_
}
var selectedSlot = 0
def actualSlot(n: Int) = n + 3
// ----------------------------------------------------------------------- //
def move(nx: Int, ny: Int, nz: Int) = {
val (ox, oy, oz) = (x, y, z)
// Setting this will make the tile entity created via the following call
// to setBlock to re-use our "real" instance as the inner object, instead
// of creating a new one.
Blocks.robot.moving.set(Some(this))
// Do *not* immediately send the change to clients to allow checking if it
// worked before the client is notified so that we can use the same trick on
// the client by sending a corresponding packet. This also saves us from
// having to send the complete state again (e.g. screen buffer) each move.
val blockId = world.getBlockId(nx, ny, nz)
val metadata = world.getBlockMetadata(nx, ny, nz)
val created = world.setBlock(nx, ny, nz, getBlockType.blockID, getBlockMetadata, 1)
if (created) {
assert(world.getBlockTileEntity(nx, ny, nz) == this)
assert(x == nx && y == ny && z == nz)
if (isServer) {
ServerPacketSender.sendRobotMove(this, ox, oy, oz)
world.setBlock(ox, oy, oz, 0, 0, 1)
for (neighbor <- node.neighbors) {
node.disconnect(neighbor)
}
api.Network.joinOrCreateNetwork(world, nx, ny, nz)
}
else {
if (blockId > 0) {
world.playAuxSFX(2001, nx, ny, nz, blockId + (metadata << 12))
}
world.markBlockForUpdate(x, y, z)
world.setBlockToAir(ox, oy, oz)
}
assert(!isInvalid)
}
Blocks.robot.moving.set(None)
if (created) {
checkRedstoneInputChanged()
}
created
}
def animateMove(direction: ForgeDirection, duration: Double) {}
def animateTurn(oldFacing: ForgeDirection, duration: Double) {}
override def installedMemory = 64 * 1024
def tier = 0
//def bounds =
override def installedMemory = 64 * 1024
def actualSlot(n: Int) = n + 3
// ----------------------------------------------------------------------- //
@LuaCallback("start")
def start(context: Context, args: Arguments): Array[AnyRef] =
Array(Boolean.box(computer.start()))
result(computer.start())
@LuaCallback("stop")
def stop(context: Context, args: Arguments): Array[AnyRef] =
Array(Boolean.box(computer.stop()))
result(computer.stop())
@LuaCallback(value = "isRunning", direct = true)
def isRunning(context: Context, args: Arguments): Array[AnyRef] =
Array(Boolean.box(computer.isRunning))
@LuaCallback(value = "isRobot", direct = true)
def isRobot(context: Context, args: Arguments): Array[AnyRef] =
Array(java.lang.Boolean.TRUE)
result(computer.isRunning)
// ----------------------------------------------------------------------- //
@ -92,6 +131,7 @@ class Robot(isRemote: Boolean) extends Computer(isRemote) with Buffer with Power
}
override def validate() {
if (Blocks.robot.moving.get.isEmpty) {
super.validate()
if (isServer) {
items(0) match {
@ -100,18 +140,20 @@ class Robot(isRemote: Boolean) extends Computer(isRemote) with Buffer with Power
}
}
else {
ClientPacketSender.sendRotatableStateRequest(this)
ClientPacketSender.sendScreenBufferRequest(this)
ClientPacketSender.sendRobotSelectedSlotRequest(this)
}
}
}
override def invalidate() {
if (Blocks.robot.moving.get.isEmpty) {
super.invalidate()
if (currentGui.isDefined) {
Minecraft.getMinecraft.displayGuiScreen(null)
}
}
}
// ----------------------------------------------------------------------- //
@ -141,16 +183,6 @@ class Robot(isRemote: Boolean) extends Computer(isRemote) with Buffer with Power
// ----------------------------------------------------------------------- //
override def onMessage(message: Message) {
if (message.source.network == node.network) {
//computer.node.network.sendToReachable(message.source, message.name, message.data: _*)
}
else {
assert(message.source.network == computer.node.network)
//node.network.sendToReachable(message.source, message.name, message.data: _*)
}
}
override def onConnect(node: Node) {
if (node == this.node) {
api.Network.joinNewNetwork(computer.node)

View File

@ -59,6 +59,22 @@ object PacketSender {
}
}
def sendRobotMove(t: Robot, ox: Int, oy: Int, oz: Int) {
val pb = new PacketBuilder(PacketType.RobotMove)
// Custom pb.writeTileEntity() with fake coordinates (valid for the client).
pb.writeInt(t.world.provider.dimensionId)
pb.writeInt(ox)
pb.writeInt(oy)
pb.writeInt(oz)
pb.writeInt(t.x)
pb.writeInt(t.y)
pb.writeInt(t.z)
pb.sendToAllPlayers()
}
def sendRobotSelectedSlotState(t: Robot, player: Option[Player] = None) {
val pb = new PacketBuilder(PacketType.RobotSelectedSlotResponse)

View File

@ -127,9 +127,12 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) {
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)
// Don't drop using the fake player because he throws too far.
if (robot.dropSlot(actualSlot(selectedSlot), count, facing)) {
context.pause(Config.dropDelay)
result(true)
}
else result(false)
}
@LuaCallback("place")
@ -160,6 +163,9 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) {
if (stack.stackSize <= 0) {
robot.setInventorySlotContents(player.robotInventory.selectedSlot, null)
}
if (what) {
context.pause(Config.placeDelay)
}
result(what)
}
}
@ -173,7 +179,10 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) {
val stack = entity.getEntityItem
val size = stack.stackSize
entity.onCollideWithPlayer(robot.player())
if (stack.stackSize < size || entity.isDead) return result(true)
if (stack.stackSize < size || entity.isDead) {
context.pause(Config.suckDelay)
return result(true)
}
}
result(false)
}
@ -183,24 +192,8 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) {
@LuaCallback("detect")
def detect(context: Context, args: Arguments): Array[AnyRef] = {
val side = checkSideForAction(args, 0)
val (bx, by, bz) = (x + side.offsetX, y + side.offsetY, z + side.offsetZ)
val id = world.getBlockId(bx, by, bz)
val block = Block.blocksList(id)
if (id == 0 || block == null || block.isAirBlock(world, bx, by, bz)) {
robot.player().closestLivingEntity(side) match {
case Some(entity) => result(true, "entity")
case _ => result(false, "air")
}
}
else if (FluidRegistry.lookupFluidForBlock(block) != null || block.isInstanceOf[BlockFluid]) {
result(false, "liquid")
}
else if (block.isBlockReplaceable(world, bx, by, bz)) {
result(false, "replaceable")
}
else {
result(true, "solid")
}
val (something, what) = blockContent(side)
result(something, what)
}
// ----------------------------------------------------------------------- //
@ -219,9 +212,13 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) {
val what = hit.typeOfHit match {
case EnumMovingObjectType.ENTITY =>
player.attackTargetEntityWithCurrentItem(hit.entityHit)
context.pause(Config.swingDelay)
result(true, "entity")
case EnumMovingObjectType.TILE =>
val broke = player.clickBlock(hit.blockX, hit.blockY, hit.blockZ, hit.sideHit)
if (broke) {
context.pause(Config.swingDelay)
}
result(broke, "block")
}
what
@ -229,6 +226,7 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) {
player.closestLivingEntity(facing) match {
case Some(entity) =>
player.attackTargetEntityWithCurrentItem(entity)
context.pause(Config.swingDelay)
result(true, "entity")
case _ =>
result(false)
@ -247,9 +245,15 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) {
val player = robot.player(facing, side)
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 ActivationType.BlockActivated =>
context.pause(Config.useDelay)
result(true, "block_activated")
case ActivationType.ItemPlaced =>
context.pause(Config.useDelay)
result(true, "item_placed")
case ActivationType.ItemUsed =>
context.pause(Config.useDelay)
result(true, "item_used")
case _ => result(false)
}
player.setSneaking(sneaky)
@ -269,7 +273,10 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) {
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")
if (player.useEquippedItem()) {
context.pause(Config.useDelay)
result(true, "item_used")
}
else result(false)
case activationType => activationResult(activationType)
}
@ -290,44 +297,60 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) {
}
}
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)
val target = Vec3.createVectorHelper(bx + hx, by + hy, bz + hz)
world.clip(origin, target)
}
// ----------------------------------------------------------------------- //
@LuaCallback("move")
def move(context: Context, args: Arguments): Array[AnyRef] = {
// Try to move in the specified direction.
val side = checkSideForMovement(args, 0)
null
val direction = checkSideForMovement(args, 0)
val (something, what) = blockContent(direction)
if (something) {
result(false, what)
}
else {
val (nx, ny, nz) = (x + direction.offsetX, y + direction.offsetY, z + direction.offsetZ)
if (robot.move(nx, ny, nz)) {
robot.animateMove(direction, Config.moveDelay)
context.pause(Config.moveDelay)
result(true)
}
else {
result(false)
}
}
}
@LuaCallback("turn")
def turn(context: Context, args: Arguments): Array[AnyRef] = {
val clockwise = args.checkBoolean(0)
val oldFacing = robot.facing
if (clockwise) robot.rotate(ForgeDirection.UP)
else robot.rotate(ForgeDirection.DOWN)
robot.animateTurn(oldFacing, 0.4)
context.pause(Config.turnDelay)
result(true)
}
// ----------------------------------------------------------------------- //
private def haveSameItemType(stackA: ItemStack, stackB: ItemStack) =
stackA.itemID == stackB.itemID &&
(!stackA.getHasSubtypes || stackA.getItemDamage == stackB.getItemDamage)
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 blockContent(side: ForgeDirection) = {
val (bx, by, bz) = (x + side.offsetX, y + side.offsetY, z + side.offsetZ)
val id = world.getBlockId(bx, by, bz)
val block = Block.blocksList(id)
if (id == 0 || block == null || block.isAirBlock(world, bx, by, bz)) {
robot.player().closestLivingEntity(side) match {
case Some(entity) => (true, "entity")
case _ => (false, "air")
}
}
else if (FluidRegistry.lookupFluidForBlock(block) != null || block.isInstanceOf[BlockFluid]) {
(false, "liquid")
}
else if (block.isBlockReplaceable(world, bx, by, bz)) {
(false, "replaceable")
}
else {
(true, "solid")
}
}
private def clickParamsFromFacing(facing: ForgeDirection, side: ForgeDirection) = {
@ -339,6 +362,29 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) {
0.5f - side.offsetZ * 0.5f)
}
private 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)
val target = Vec3.createVectorHelper(bx + hx, by + hy, bz + hz)
world.clip(origin, target)
}
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 haveSameItemType(stackA: ItemStack, stackB: ItemStack) =
stackA.itemID == stackB.itemID &&
(!stackA.getHasSubtypes || stackA.getItemDamage == stackB.getItemDamage)
private def stackInSlot(slot: Int) = Option(robot.getStackInSlot(actualSlot(slot)))
// ----------------------------------------------------------------------- //
private def checkOptionalItemCount(args: Arguments, n: Int) =
@ -357,7 +403,7 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) {
private def checkSideForAction(args: Arguments, n: Int) = checkSide(args, n, ForgeDirection.SOUTH, ForgeDirection.UP, ForgeDirection.DOWN)
private def checkSideForMovement(args: Arguments, n: Int) = checkSide(args, n, ForgeDirection.SOUTH, ForgeDirection.NORTH)
private def checkSideForMovement(args: Arguments, n: Int) = checkSide(args, n, ForgeDirection.SOUTH, ForgeDirection.NORTH, ForgeDirection.UP, ForgeDirection.DOWN)
private def checkSide(args: Arguments, n: Int, allowed: ForgeDirection*) = {
val side = args.checkInteger(n)

View File

@ -247,7 +247,7 @@ object Network extends api.detail.NetworkAPI {
case Some(node: MutableNode) => {
for (side <- ForgeDirection.VALID_DIRECTIONS) {
getNetworkNode(world, x + side.offsetX, y + side.offsetY, z + side.offsetZ) match {
case Some(neighbor) =>
case Some(neighbor: MutableNode) if neighbor != node =>
if (neighbor.network != null) {
neighbor.connect(node)
}