diff --git a/li/cil/oc/Config.scala b/li/cil/oc/Config.scala index a1c0de7bb..f2bcfff07 100644 --- a/li/cil/oc/Config.scala +++ b/li/cil/oc/Config.scala @@ -83,7 +83,7 @@ object Config { var callOnItemUseFirst = false var allowActivateBlocks = true var canAttackPlayers = false - var itemDamageChance = 0.05 + var itemDamageRate = 0.05 // ----------------------------------------------------------------------- // diff --git a/li/cil/oc/common/tileentity/Robot.scala b/li/cil/oc/common/tileentity/Robot.scala index 66b24f2aa..f169f92e2 100644 --- a/li/cil/oc/common/tileentity/Robot.scala +++ b/li/cil/oc/common/tileentity/Robot.scala @@ -93,7 +93,13 @@ class Robot(isRemote: Boolean) extends Computer(isRemote) with Buffer with Power override def validate() { super.validate() - if (isClient) { + if (isServer) { + items(0) match { + case Some(item) => player_.getAttributeMap.applyAttributeModifiers(item.getAttributeModifiers) + case _ => + } + } + else { ClientPacketSender.sendRotatableStateRequest(this) ClientPacketSender.sendScreenBufferRequest(this) ClientPacketSender.sendRobotSelectedSlotRequest(this) @@ -198,8 +204,18 @@ class Robot(isRemote: Boolean) extends Computer(isRemote) with Buffer with Power case _ => false // Invalid slot. } + override protected def onItemRemoved(slot: Int, item: ItemStack) { + super.onItemRemoved(slot, item) + if (slot == 0) { + player_.getAttributeMap.removeAttributeModifiers(item.getAttributeModifiers) + } + } + override protected def onItemAdded(slot: Int, item: ItemStack) { - if (slot > 0 && slot < 3) { + if (slot == 0) { + player_.getAttributeMap.applyAttributeModifiers(item.getAttributeModifiers) + } + else if (slot == 1 || slot == 2) { super.onItemAdded(slot, item) } } diff --git a/li/cil/oc/server/component/Robot.scala b/li/cil/oc/server/component/Robot.scala index 2ecae4999..d8fa0e031 100644 --- a/li/cil/oc/server/component/Robot.scala +++ b/li/cil/oc/server/component/Robot.scala @@ -3,9 +3,11 @@ package li.cil.oc.server.component import li.cil.oc.api.network.{LuaCallback, Arguments, Context} import li.cil.oc.common.tileentity import li.cil.oc.server.{PacketSender => ServerPacketSender} +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.minecraftforge.common.ForgeDirection import net.minecraftforge.fluids.FluidRegistry import scala.Some @@ -40,7 +42,7 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) { ServerPacketSender.sendRobotSelectedSlotState(robot) } } - result(selectedSlot) + result(selectedSlot + 1) } @LuaCallback("count") @@ -200,32 +202,91 @@ class Robot(val robot: tileentity.Robot) extends Computer(robot) { @LuaCallback("swing") def swing(context: Context, args: Arguments): Array[AnyRef] = { // Swing the equipped tool (left click). - val lookSide = checkSideForAction(args, 0) - val side = if (args.isInteger(1)) checkSide(args, 1) else lookSide - if (side.getOpposite == lookSide) { + 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 player = robot.player(lookSide) - val (bx, by, bz) = (x + lookSide.offsetX, y + lookSide.offsetY, z + lookSide.offsetZ) - result(player.clickBlock(bx, by, bz, side.getOpposite.ordinal)) - //robot.player().attackTargetEntityWithCurrentItem(entity) + val sneaky = args.isBoolean(2) && args.checkBoolean(2) + val player = robot.player(facing) + Option(simulateClick(facing, side, 0.49)) match { + case Some(hit) => + player.setSneaking(sneaky) + val what = hit.typeOfHit match { + case EnumMovingObjectType.ENTITY => + player.attackTargetEntityWithCurrentItem(hit.entityHit) + result(true, "entity") + case EnumMovingObjectType.TILE => + val broke = player.clickBlock(hit.blockX, hit.blockY, hit.blockZ, hit.sideHit) + result(broke, "block") + } + player.setSneaking(false) + what + case _ => + player.closestLivingEntity(facing) match { + case Some(entity) => + player.attackTargetEntityWithCurrentItem(entity) + result(true, "entity") + case _ => + result(false) + } + } } @LuaCallback("use") def use(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) { + 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(lookSide) - 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.activateBlockOrUseItem(bx, by, bz, side.getOpposite.ordinal, hx, hy, hz) - player.setSneaking(false) - result(ok) + val player = robot.player(facing) + Option(simulateClick(facing, side, 0.51)) match { + case Some(hit) => + 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.ItemPlaced => result(true, "item_placed") + case ActivationType.ItemUsed => result(true, "item_used") + case _ => result(false) + } + } + player.setSneaking(false) + what + case _ => + if (player.useEquippedItem()) result(true, "item_used") + else result(false) + } + } + + @LuaCallback("durability") + def durability(context: Context, args: Arguments): Array[AnyRef] = { + Option(robot.getStackInSlot(0)) match { + case Some(item) => + if (item.isItemStackDamageable) { + result(item.getMaxDamage - item.getItemDamage) + } + else result(Unit, "tool cannot be damaged") + case _ => result(Unit, "no tool equipped") + } + } + + def simulateClick(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) } // ----------------------------------------------------------------------- // diff --git a/li/cil/oc/util/ActivationType.scala b/li/cil/oc/util/ActivationType.scala new file mode 100644 index 000000000..5bd9d81d1 --- /dev/null +++ b/li/cil/oc/util/ActivationType.scala @@ -0,0 +1,5 @@ +package li.cil.oc.util + +object ActivationType extends Enumeration { + val None, ItemUsed, ItemPlaced, BlockActivated = Value +} diff --git a/li/cil/oc/util/RobotPlayer.scala b/li/cil/oc/util/RobotPlayer.scala index 85929b7ef..7ed99196b 100644 --- a/li/cil/oc/util/RobotPlayer.scala +++ b/li/cil/oc/util/RobotPlayer.scala @@ -66,12 +66,7 @@ class RobotPlayer(val robot: Robot) extends FakePlayer(robot.world, "OpenCompute def entitiesOnSide[Type <: Entity : ClassTag](side: ForgeDirection) = { val (bx, by, bz) = (robot.x + side.offsetX, robot.y + side.offsetY, robot.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)) { - entitiesInBlock[Type](bx, by, bz) - } - else Iterable.empty + entitiesInBlock[Type](bx, by, bz) } def entitiesInBlock[Type <: Entity : ClassTag](x: Int, y: Int, z: Int) = { @@ -81,14 +76,20 @@ class RobotPlayer(val robot: Robot) extends FakePlayer(robot.world, "OpenCompute // ----------------------------------------------------------------------- // - // TODO override def canAttackWithItem = super.canAttackWithItem + override def attackTargetEntityWithCurrentItem(entity: Entity) { + val stack = getCurrentEquippedItem + val oldDamage = getCurrentEquippedItem.getItemDamage + super.attackTargetEntityWithCurrentItem(entity) + if (stack.stackSize > 0 && stack.isItemStackDamageable && getRNG.nextDouble() >= Config.itemDamageRate) { + val addedDamage = ((stack.getItemDamage - oldDamage) * Config.itemDamageRate).toInt + stack.setItemDamage(oldDamage + addedDamage) + } + } - // TODO override def attackTargetEntityWithCurrentItem(par1Entity: Entity) - - def activateBlockOrUseItem(x: Int, y: Int, z: Int, side: Int, hitX: Float, hitY: Float, hitZ: Float): Boolean = { + def activateBlockOrUseItem(x: Int, y: Int, z: Int, side: Int, hitX: Float, hitY: Float, hitZ: Float): ActivationType.Value = { val event = ForgeEventFactory.onPlayerInteract(this, Action.RIGHT_CLICK_BLOCK, x, y, z, side) - if (event.isCanceled) { - return false + if (event.isCanceled || event.useBlock == Event.Result.DENY) { + return ActivationType.None } val stack = inventory.getCurrentItem @@ -97,7 +98,7 @@ class RobotPlayer(val robot: Robot) extends FakePlayer(robot.world, "OpenCompute if (item != null && item.onItemUseFirst(stack, this, world, x, y, z, side, hitX, hitY, hitZ)) { if (stack.stackSize <= 0) ForgeEventFactory.onPlayerDestroyItem(this, stack) if (stack.stackSize <= 0) inventory.setInventorySlotContents(0, null) - return true + return ActivationType.ItemUsed } } @@ -105,17 +106,36 @@ class RobotPlayer(val robot: Robot) extends FakePlayer(robot.world, "OpenCompute val block = Block.blocksList(blockId) val canActivate = block != null && Config.allowActivateBlocks val shouldActivate = canActivate && (!isSneaking || (item == null || item.shouldPassSneakingClickToBlock(world, x, y, z))) - val activated = shouldActivate && (event.useBlock == Event.Result.DENY || - block.onBlockActivated(world, x, y, z, this, side, hitX, hitY, hitZ)) + if (shouldActivate && block.onBlockActivated(world, x, y, z, this, side, hitX, hitY, hitZ)) { + return ActivationType.BlockActivated + } - activated || (stack != null && ({ - 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) + if (stack != null) { + val didPlace = stack.tryPlaceItemIntoWorld(this, world, x, y, z, side, hitX, hitY, hitZ) if (stack.stackSize <= 0) ForgeEventFactory.onPlayerDestroyItem(this, stack) if (stack.stackSize <= 0) inventory.setInventorySlotContents(0, null) - result - } || stack.getMaxItemUseDuration <= 0 && { + if (didPlace) { + return ActivationType.ItemPlaced + } + + if (tryUseItem(stack)) { + return ActivationType.ItemUsed + } + } + + ActivationType.None + } + + def useEquippedItem() = { + val event = ForgeEventFactory.onPlayerInteract(this, Action.RIGHT_CLICK_AIR, 0, 0, 0, -1) + if (!event.isCanceled && event.useItem != Event.Result.DENY) { + tryUseItem(getCurrentEquippedItem) + } + else false + } + + private def tryUseItem(stack: ItemStack) = + stack != null && stack.stackSize > 0 && stack.getMaxItemUseDuration <= 0 && { val oldSize = stack.stackSize val oldDamage = stack.getItemDamage val newStack = stack.useItemRightClick(world, this) @@ -126,8 +146,7 @@ class RobotPlayer(val robot: Robot) extends FakePlayer(robot.world, "OpenCompute else inventory.setInventorySlotContents(0, null) true } - })) - } + } def placeBlock(stack: ItemStack, x: Int, y: Int, z: Int, side: Int, hitX: Float, hitY: Float, hitZ: Float): Boolean = { val event = ForgeEventFactory.onPlayerInteract(this, Action.RIGHT_CLICK_BLOCK, x, y, z, side) @@ -184,16 +203,17 @@ class RobotPlayer(val robot: Robot) extends FakePlayer(robot.world, "OpenCompute if (stack.stackSize == 0) { destroyCurrentEquippedItem() } - else if (stack.isItemStackDamageable && getRNG.nextDouble() >= Config.itemDamageChance) { - stack.setItemDamage(oldDamage) + else if (stack.isItemStackDamageable && getRNG.nextDouble() >= Config.itemDamageRate) { + val addedDamage = ((stack.getItemDamage - oldDamage) * Config.itemDamageRate).toInt + stack.setItemDamage(oldDamage + addedDamage) } } + val itemsBefore = entitiesInBlock[EntityItem](x, y, z) block.onBlockHarvested(world, x, y, z, metadata, this) if (block.removeBlockByPlayer(world, this, x, y, z)) { block.onBlockDestroyedByPlayer(world, x, y, z, metadata) if (block.canHarvestBlock(this, metadata)) { - val itemsBefore = entitiesInBlock[EntityItem](x, y, z) block.harvestBlock(world, this, x, y, z, metadata) val itemsAfter = entitiesInBlock[EntityItem](x, y, z) val itemsDropped = itemsAfter -- itemsBefore