diff --git a/li/cil/oc/Config.scala b/li/cil/oc/Config.scala index 8ac5f5c8e..64ada22c3 100644 --- a/li/cil/oc/Config.scala +++ b/li/cil/oc/Config.scala @@ -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) = { diff --git a/li/cil/oc/client/PacketHandler.scala b/li/cil/oc/client/PacketHandler.scala index 805dde8b5..25072b762 100644 --- a/li/cil/oc/client/PacketHandler.scala +++ b/li/cil/oc/client/PacketHandler.scala @@ -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() diff --git a/li/cil/oc/common/PacketType.scala b/li/cil/oc/common/PacketType.scala index 069777c40..05670383b 100644 --- a/li/cil/oc/common/PacketType.scala +++ b/li/cil/oc/common/PacketType.scala @@ -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") diff --git a/li/cil/oc/common/block/Delegate.scala b/li/cil/oc/common/block/Delegate.scala index b9d7c49c8..066454292 100644 --- a/li/cil/oc/common/block/Delegate.scala +++ b/li/cil/oc/common/block/Delegate.scala @@ -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 diff --git a/li/cil/oc/common/block/Delegator.scala b/li/cil/oc/common/block/Delegator.scala index ee130bc2e..31875775f 100644 --- a/li/cil/oc/common/block/Delegator.scala +++ b/li/cil/oc/common/block/Delegator.scala @@ -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) diff --git a/li/cil/oc/common/block/Robot.scala b/li/cil/oc/common/block/Robot.scala index 8af99b960..1db487b6f 100644 --- a/li/cil/oc/common/block/Robot.scala +++ b/li/cil/oc/common/block/Robot.scala @@ -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) + } + } } diff --git a/li/cil/oc/common/tileentity/Robot.scala b/li/cil/oc/common/tileentity/Robot.scala index 0a9e72550..4bd89416c 100644 --- a/li/cil/oc/common/tileentity/Robot.scala +++ b/li/cil/oc/common/tileentity/Robot.scala @@ -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,24 +131,27 @@ class Robot(isRemote: Boolean) extends Computer(isRemote) with Buffer with Power } override def validate() { - super.validate() - if (isServer) { - items(0) match { - case Some(item) => player_.getAttributeMap.applyAttributeModifiers(item.getAttributeModifiers) - case _ => + if (Blocks.robot.moving.get.isEmpty) { + super.validate() + if (isServer) { + items(0) match { + case Some(item) => player_.getAttributeMap.applyAttributeModifiers(item.getAttributeModifiers) + case _ => + } + } + else { + ClientPacketSender.sendScreenBufferRequest(this) + ClientPacketSender.sendRobotSelectedSlotRequest(this) } - } - else { - ClientPacketSender.sendRotatableStateRequest(this) - ClientPacketSender.sendScreenBufferRequest(this) - ClientPacketSender.sendRobotSelectedSlotRequest(this) } } override def invalidate() { - super.invalidate() - if (currentGui.isDefined) { - Minecraft.getMinecraft.displayGuiScreen(null) + 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) diff --git a/li/cil/oc/server/PacketSender.scala b/li/cil/oc/server/PacketSender.scala index f89e2d19c..7a8662bb3 100644 --- a/li/cil/oc/server/PacketSender.scala +++ b/li/cil/oc/server/PacketSender.scala @@ -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) diff --git a/li/cil/oc/server/component/Robot.scala b/li/cil/oc/server/component/Robot.scala index 7f657f7e9..03ed2a49d 100644 --- a/li/cil/oc/server/component/Robot.scala +++ b/li/cil/oc/server/component/Robot.scala @@ -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) diff --git a/li/cil/oc/server/network/Network.scala b/li/cil/oc/server/network/Network.scala index ecdb6b291..9e193b877 100644 --- a/li/cil/oc/server/network/Network.scala +++ b/li/cil/oc/server/network/Network.scala @@ -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) }