blob: 6702b553b778b48c84259152af33e17812da5c4e [file] [log] [blame] [raw]
package li.cil.oc.server.component
import li.cil.oc.OpenComputers
import li.cil.oc.Settings
import li.cil.oc.api
import li.cil.oc.api.event.RobotPlaceInAirEvent
import li.cil.oc.api.machine.Arguments
import li.cil.oc.api.machine.Callback
import li.cil.oc.api.machine.Context
import li.cil.oc.api.network._
import li.cil.oc.api.prefab
import li.cil.oc.common.ToolDurabilityProviders
import li.cil.oc.common.tileentity
import li.cil.oc.server.agent.ActivationType
import li.cil.oc.server.agent.Player
import li.cil.oc.util.BlockPosition
import li.cil.oc.util.ExtendedArguments._
import li.cil.oc.util.ExtendedNBT._
import li.cil.oc.util.ExtendedWorld._
import net.minecraft.entity.Entity
import net.minecraft.entity.EntityLivingBase
import net.minecraft.entity.item.EntityMinecart
import net.minecraft.nbt.NBTTagCompound
import net.minecraft.util.MovingObjectPosition
import net.minecraft.util.MovingObjectPosition.MovingObjectType
import net.minecraft.util.Vec3
import net.minecraftforge.common.MinecraftForge
import net.minecraftforge.common.util.ForgeDirection
import scala.collection.convert.WrapAsScala._
class Robot(val robot: tileentity.Robot) extends prefab.ManagedEnvironment with traits.WorldControl with traits.InventoryControl with traits.InventoryWorldControl with traits.TankControl with traits.TankWorldControl {
override val node = api.Network.newNode(this, Visibility.Network).
withComponent("robot").
withConnector(Settings.get.bufferRobot).
create()
val romRobot = Option(api.FileSystem.asManagedEnvironment(api.FileSystem.
fromClass(OpenComputers.getClass, Settings.resourceDomain, "lua/component/robot"), "robot"))
override def position = BlockPosition(robot)
// ----------------------------------------------------------------------- //
override def inventory = robot.mainInventory
override def selectedSlot = robot.selectedSlot
override def selectedSlot_=(value: Int): Unit = robot.setSelectedSlot(value)
// ----------------------------------------------------------------------- //
override def tank = robot.tank
def selectedTank = robot.selectedTank
override def selectedTank_=(value: Int) = robot.selectedTank = value
// ----------------------------------------------------------------------- //
override def fakePlayer = robot.player()
override protected def checkSideForAction(args: Arguments, n: Int) = robot.toGlobal(args.checkSideForAction(n))
private def checkSideForFace(args: Arguments, n: Int, facing: ForgeDirection) = robot.toGlobal(args.checkSideForFace(n, robot.toLocal(facing)))
// ----------------------------------------------------------------------- //
def canPlaceInAir = {
val event = new RobotPlaceInAirEvent(robot)
MinecraftForge.EVENT_BUS.post(event)
event.isAllowed
}
// ----------------------------------------------------------------------- //
@Callback
def name(context: Context, args: Arguments): Array[AnyRef] = result(robot.name)
@Callback(doc = "function():number -- Get the current color of the activity light as an integer encoded RGB value (0xRRGGBB).")
def getLightColor(context: Context, args: Arguments): Array[AnyRef] = result(robot.info.lightColor)
@Callback(doc = "function(value:number):number -- Set the color of the activity light to the specified integer encoded RGB value (0xRRGGBB).")
def setLightColor(context: Context, args: Arguments): Array[AnyRef] = {
robot.setLightColor(args.checkInteger(0))
context.pause(0.1)
result(robot.info.lightColor)
}
// ----------------------------------------------------------------------- //
@Callback
def place(context: Context, args: Arguments): Array[AnyRef] = {
val facing = checkSideForAction(args, 0)
val sides =
if (args.isInteger(1)) {
Iterable(checkSideForFace(args, 1, facing))
}
else {
// Always try the direction we're looking first.
Iterable(facing) ++ ForgeDirection.VALID_DIRECTIONS.filter(side => side != facing && side != facing.getOpposite).toIterable
}
val sneaky = args.isBoolean(2) && args.checkBoolean(2)
val stack = robot.inventory.selectedItemStack
if (stack == null || stack.stackSize == 0) {
return result(Unit, "nothing selected")
}
for (side <- sides) {
val player = robot.player(facing, side)
player.setSneaking(sneaky)
val success = Option(pick(player, Settings.get.useAndPlaceRange)) match {
case Some(hit) if hit.typeOfHit == MovingObjectType.BLOCK =>
val (bx, by, bz, hx, hy, hz) = clickParamsFromHit(hit)
player.placeBlock(robot.selectedSlot, bx, by, bz, hit.sideHit, hx, hy, hz)
case None if canPlaceInAir && player.closestEntity[Entity]().isEmpty =>
val (bx, by, bz, hx, hy, hz) = clickParamsForPlace(facing)
player.placeBlock(robot.selectedSlot, bx, by, bz, facing.ordinal, hx, hy, hz)
case _ => false
}
player.setSneaking(false)
if (success) {
context.pause(Settings.get.placeDelay)
robot.animateSwing(Settings.get.placeDelay)
return result(true)
}
}
result(false)
}
// ----------------------------------------------------------------------- //
@Callback
def swing(context: Context, args: Arguments): Array[AnyRef] = {
// Swing the equipped tool (left click).
val facing = checkSideForAction(args, 0)
val sides =
if (args.isInteger(1)) {
Iterable(checkSideForFace(args, 1, facing))
}
else {
// Always try the direction we're looking first.
Iterable(facing) ++ ForgeDirection.VALID_DIRECTIONS.filter(side => side != facing && side != facing.getOpposite).toIterable
}
val sneaky = args.isBoolean(2) && args.checkBoolean(2)
def triggerDelay(delay: Double = Settings.get.swingDelay) = {
context.pause(delay)
robot.animateSwing(Settings.get.swingDelay)
}
def attack(player: Player, entity: Entity) = {
beginConsumeDrops(entity)
player.attackTargetEntityWithCurrentItem(entity)
// Mine carts have to be hit quickly in succession to break, so we click
// until it breaks. But avoid an infinite loop... you never know.
entity match {
case _: EntityMinecart => for (_ <- 0 until 10 if !entity.isDead) {
player.attackTargetEntityWithCurrentItem(entity)
}
case _ =>
}
endConsumeDrops(player, entity)
triggerDelay()
(true, "entity")
}
def click(player: Player, x: Int, y: Int, z: Int, side: Int) = {
val breakTime = player.clickBlock(x, y, z, side)
val broke = breakTime > 0
if (broke) {
// Subtract one tick because we take one to trigger the action - a bit
// more than one tick avoid floating point inaccuracy incurring another
// tick of delay.
triggerDelay(breakTime - 0.055)
}
(broke, "block")
}
var reason: Option[String] = None
for (side <- sides) {
val player = robot.player(facing, side)
player.setSneaking(sneaky)
val (success, what) = {
val hit = pick(player, Settings.get.swingRange)
(Option(hit) match {
case Some(info) => info.typeOfHit
case _ => MovingObjectType.MISS
}) match {
case MovingObjectType.ENTITY =>
attack(player, hit.entityHit)
case MovingObjectType.BLOCK =>
click(player, hit.blockX, hit.blockY, hit.blockZ, hit.sideHit)
case _ =>
// Retry with full block bounds, disregarding swing range.
player.closestEntity[EntityLivingBase]() match {
case Some(entity) =>
attack(player, entity)
case _ =>
if (world.extinguishFire(player, position, facing)) {
triggerDelay()
(true, "fire")
}
else (false, "air")
}
}
}
player.setSneaking(false)
if (success) {
return result(true, what)
}
reason = reason.orElse(Option(what))
}
result(false, reason.orNull)
}
@Callback
def use(context: Context, args: Arguments): Array[AnyRef] = {
val facing = checkSideForAction(args, 0)
val sides =
if (args.isInteger(1)) {
Iterable(checkSideForFace(args, 1, facing))
}
else {
// Always try the direction we're looking first.
Iterable(facing) ++ ForgeDirection.VALID_DIRECTIONS.filter(side => side != facing && side != facing.getOpposite).toIterable
}
val sneaky = args.isBoolean(2) && args.checkBoolean(2)
val duration =
if (args.isDouble(3)) args.checkDouble(3)
else 0.0
def triggerDelay() {
context.pause(Settings.get.useDelay)
robot.animateSwing(Settings.get.useDelay)
}
def activationResult(activationType: ActivationType.Value) =
activationType match {
case ActivationType.BlockActivated =>
triggerDelay()
(true, "block_activated")
case ActivationType.ItemPlaced =>
triggerDelay()
(true, "item_placed")
case ActivationType.ItemUsed =>
triggerDelay()
(true, "item_used")
case _ => (false, "")
}
def interact(player: Player, entity: Entity) = {
beginConsumeDrops(entity)
val result = player.interactWith(entity)
endConsumeDrops(player, entity)
result
}
for (side <- sides) {
val player = robot.player(facing, side)
player.setSneaking(sneaky)
val (success, what) = Option(pick(player, Settings.get.useAndPlaceRange)) match {
case Some(hit) if hit.typeOfHit == MovingObjectType.ENTITY && interact(player, hit.entityHit) =>
triggerDelay()
(true, "item_interacted")
case Some(hit) if hit.typeOfHit == MovingObjectType.BLOCK =>
val (bx, by, bz, hx, hy, hz) = clickParamsFromHit(hit)
activationResult(player.activateBlockOrUseItem(bx, by, bz, hit.sideHit, hx, hy, hz, duration))
case _ =>
(if (canPlaceInAir) {
val (bx, by, bz, hx, hy, hz) = clickParamsForPlace(facing)
if (player.placeBlock(0, bx, by, bz, facing.ordinal, hx, hy, hz))
ActivationType.ItemPlaced
else {
val (bx, by, bz, hx, hy, hz) = clickParamsForItemUse(facing, side)
player.activateBlockOrUseItem(bx, by, bz, side.getOpposite.ordinal, hx, hy, hz, duration)
}
} else ActivationType.None) match {
case ActivationType.None =>
if (player.useEquippedItem(duration)) {
triggerDelay()
(true, "item_used")
}
else (false, "air")
case activationType => activationResult(activationType)
}
}
player.setSneaking(false)
if (success) {
return result(true, what)
}
}
result(false)
}
@Callback
def durability(context: Context, args: Arguments): Array[AnyRef] = {
Option(robot.getStackInSlot(0)) match {
case Some(item) =>
ToolDurabilityProviders.getDurability(item) match {
case Some(durability) => result(durability)
case _ => result(Unit, "tool cannot be damaged")
}
case _ => result(Unit, "no tool equipped")
}
}
// ----------------------------------------------------------------------- //
@Callback
def move(context: Context, args: Arguments): Array[AnyRef] = {
val direction = robot.toGlobal(args.checkSideForMovement(0))
if (robot.isAnimatingMove) {
// This shouldn't really happen due to delays being enforced, but just to
// be on the safe side...
result(Unit, "already moving")
}
else {
val (something, what) = blockContent(direction)
if (something) {
result(Unit, what)
}
else {
if (!node.tryChangeBuffer(-Settings.get.robotMoveCost)) {
result(Unit, "not enough energy")
}
else if (robot.move(direction)) {
context.pause(Settings.get.moveDelay)
result(true)
}
else {
node.changeBuffer(Settings.get.robotMoveCost)
result(Unit, "impossible move")
}
}
}
}
@Callback
def turn(context: Context, args: Arguments): Array[AnyRef] = {
val clockwise = args.checkBoolean(0)
if (node.tryChangeBuffer(-Settings.get.robotTurnCost)) {
if (clockwise) robot.rotate(ForgeDirection.UP)
else robot.rotate(ForgeDirection.DOWN)
robot.animateTurn(clockwise, Settings.get.turnDelay)
context.pause(Settings.get.turnDelay)
result(true)
}
else {
result(Unit, "not enough energy")
}
}
// ----------------------------------------------------------------------- //
override def onConnect(node: Node) {
super.onConnect(node)
if (node == this.node) {
romRobot.foreach(fs => {
fs.node.asInstanceOf[Component].setVisibility(Visibility.Network)
node.connect(fs.node)
})
}
}
override def onMessage(message: Message) {
super.onMessage(message)
if (message.name == "network.message" && message.source != robot.node) message.data match {
case Array(packet: Packet) => robot.proxy.node.sendToReachable(message.name, packet)
case _ =>
}
}
// ----------------------------------------------------------------------- //
override def load(nbt: NBTTagCompound) {
super.load(nbt)
romRobot.foreach(_.load(nbt.getCompoundTag("romRobot")))
}
override def save(nbt: NBTTagCompound) {
super.save(nbt)
romRobot.foreach(fs => nbt.setNewCompoundTag("romRobot", fs.save))
}
// ----------------------------------------------------------------------- //
private def beginConsumeDrops(entity: Entity) {
entity.captureDrops = true
}
private def endConsumeDrops(player: Player, entity: Entity) {
entity.captureDrops = false
for (drop <- entity.capturedDrops) {
val stack = drop.getEntityItem
player.inventory.addItemStackToInventory(stack)
if (stack.stackSize > 0) {
player.dropPlayerItemWithRandomChoice(stack, inPlace = false)
}
}
entity.capturedDrops.clear()
}
// ----------------------------------------------------------------------- //
private def pick(player: Player, range: Double) = {
val origin = Vec3.createVectorHelper(
player.posX + player.facing.offsetX * 0.5,
player.posY + player.facing.offsetY * 0.5,
player.posZ + player.facing.offsetZ * 0.5)
val blockCenter = origin.addVector(
player.facing.offsetX * 0.5,
player.facing.offsetY * 0.5,
player.facing.offsetZ * 0.5)
val target = blockCenter.addVector(
player.side.offsetX * range,
player.side.offsetY * range,
player.side.offsetZ * range)
val hit = world.rayTraceBlocks(origin, target)
player.closestEntity[Entity]() match {
case Some(entity@(_: EntityLivingBase | _: EntityMinecart)) if hit == null || Vec3.createVectorHelper(player.posX, player.posY, player.posZ).distanceTo(hit.hitVec) > player.getDistanceToEntity(entity) => new MovingObjectPosition(entity)
case _ => hit
}
}
private def clickParamsForPlace(facing: ForgeDirection) = {
(position.x, position.y, position.z,
0.5f + facing.offsetX * 0.5f,
0.5f + facing.offsetY * 0.5f,
0.5f + facing.offsetZ * 0.5f)
}
private def clickParamsForItemUse(facing: ForgeDirection, side: ForgeDirection) = {
val blockPos = position.offset(facing).offset(side)
(blockPos.x, blockPos.y, blockPos.z,
0.5f - side.offsetX * 0.5f,
0.5f - side.offsetY * 0.5f,
0.5f - side.offsetZ * 0.5f)
}
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)
}
}