| package li.cil.oc.integration.fmp |
| |
| import java.lang |
| |
| import codechicken.lib.data.MCDataInput |
| import codechicken.lib.data.MCDataOutput |
| import codechicken.lib.raytracer.ExtendedMOP |
| import codechicken.lib.vec.Cuboid6 |
| import codechicken.lib.vec.Vector3 |
| import codechicken.multipart.IRedstonePart |
| import codechicken.multipart.PartMap |
| import codechicken.multipart.TCuboidPart |
| import codechicken.multipart.TEdgePart |
| import codechicken.multipart.TFacePart |
| import codechicken.multipart.TMultiPart |
| import codechicken.multipart.TNormalOcclusion |
| import codechicken.multipart.TSlottedPart |
| import codechicken.multipart.scalatraits.TSlottedTile |
| import cpw.mods.fml.relauncher.Side |
| import cpw.mods.fml.relauncher.SideOnly |
| import li.cil.oc.Constants |
| import li.cil.oc.Settings |
| import li.cil.oc.api.Items |
| import li.cil.oc.client.renderer.block.Print |
| import li.cil.oc.common.block.Print |
| import li.cil.oc.common.item.data.PrintData |
| import li.cil.oc.common.tileentity |
| import li.cil.oc.integration.util.BundledRedstone |
| import li.cil.oc.util.BlockPosition |
| import li.cil.oc.util.ExtendedAABB |
| import li.cil.oc.util.ExtendedAABB._ |
| import li.cil.oc.util.ExtendedNBT._ |
| import net.minecraft.client.renderer.OpenGlHelper |
| import net.minecraft.client.renderer.RenderBlocks |
| import net.minecraft.client.renderer.RenderGlobal |
| import net.minecraft.entity.player.EntityPlayer |
| import net.minecraft.item.ItemStack |
| import net.minecraft.nbt.NBTTagCompound |
| import net.minecraft.util.MovingObjectPosition |
| import net.minecraft.util.Vec3 |
| import net.minecraftforge.common.util.ForgeDirection |
| import org.lwjgl.opengl.GL11 |
| |
| import scala.collection.convert.WrapAsJava._ |
| import scala.collection.convert.WrapAsScala._ |
| |
| object PrintPart { |
| private val q0 = 0 / 16f |
| private val q1 = 4 / 16f |
| private val q2 = 12 / 16f |
| private val q3 = 16 / 16f |
| |
| val slotBounds = Array( |
| new Cuboid6(q1, q0, q1, q2, q1, q2), |
| new Cuboid6(q1, q2, q1, q2, q3, q2), |
| new Cuboid6(q1, q1, q0, q2, q2, q1), |
| new Cuboid6(q1, q1, q2, q2, q2, q3), |
| new Cuboid6(q0, q1, q1, q1, q2, q2), |
| new Cuboid6(q2, q1, q1, q3, q2, q2), |
| new Cuboid6(q1, q1, q1, q2, q2, q2), |
| |
| new Cuboid6(q0, q0, q0, q1, q1, q1), |
| new Cuboid6(q0, q2, q0, q1, q3, q1), |
| new Cuboid6(q0, q0, q2, q1, q1, q3), |
| new Cuboid6(q0, q2, q2, q1, q3, q3), |
| new Cuboid6(q2, q0, q0, q3, q1, q1), |
| new Cuboid6(q2, q2, q0, q3, q3, q1), |
| new Cuboid6(q2, q0, q2, q3, q1, q3), |
| new Cuboid6(q2, q2, q2, q3, q3, q3), |
| |
| new Cuboid6(q0, q1, q0, q1, q2, q1), |
| new Cuboid6(q0, q1, q2, q1, q2, q3), |
| new Cuboid6(q2, q1, q0, q3, q2, q1), |
| new Cuboid6(q2, q1, q2, q3, q2, q3), |
| new Cuboid6(q0, q0, q1, q1, q1, q2), |
| new Cuboid6(q2, q0, q1, q3, q1, q2), |
| new Cuboid6(q0, q2, q1, q1, q3, q2), |
| new Cuboid6(q2, q2, q1, q3, q3, q2), |
| new Cuboid6(q1, q0, q0, q2, q1, q1), |
| new Cuboid6(q1, q2, q0, q2, q3, q1), |
| new Cuboid6(q1, q0, q2, q2, q1, q3), |
| new Cuboid6(q1, q2, q2, q2, q3, q3) |
| ) |
| } |
| |
| class PrintPart(val original: Option[tileentity.Print] = None) extends SimpleBlockPart with TCuboidPart with TNormalOcclusion with IRedstonePart with TSlottedPart with TEdgePart with TFacePart { |
| var facing = ForgeDirection.SOUTH |
| var data = new PrintData() |
| |
| var boundsOff = ExtendedAABB.unitBounds |
| var boundsOn = ExtendedAABB.unitBounds |
| var state = false |
| var toggling = false // avoid infinite loops when updating neighbors |
| |
| original.foreach(print => { |
| facing = print.facing |
| data = print.data |
| boundsOff = print.boundsOff |
| boundsOn = print.boundsOn |
| state = print.state |
| }) |
| |
| // ----------------------------------------------------------------------- // |
| |
| override def simpleBlock = Items.get(Constants.BlockName.Print).block().asInstanceOf[Print] |
| |
| def getType = Settings.namespace + Constants.BlockName.Print |
| |
| override def doesTick = false |
| |
| override def getLightValue: Int = data.lightLevel |
| |
| override def getBounds = new Cuboid6(if (state) boundsOn else boundsOff) |
| |
| override def getOcclusionBoxes = { |
| val shapes = if (state) data.stateOn else data.stateOff |
| asJavaIterable(shapes.map(shape => new Cuboid6(shape.bounds.rotateTowards(facing)))) |
| } |
| |
| override def getCollisionBoxes = getOcclusionBoxes |
| |
| override def getRenderBounds = getBounds |
| |
| override def getSlotMask: Int = { |
| var mask = 0 |
| val boxes = getOcclusionBoxes |
| for (slot <- PartMap.values) { |
| val bounds = PrintPart.slotBounds(slot.i) |
| if (boxes.exists(_.intersects(bounds))) { |
| mask |= slot.mask |
| } |
| } |
| mask |
| } |
| |
| // ----------------------------------------------------------------------- // |
| |
| override def conductsRedstone: Boolean = if (state) data.emitRedstoneWhenOn else data.emitRedstoneWhenOff |
| |
| override def redstoneConductionMap: Int = if (conductsRedstone) 0xFF else 0 |
| |
| override def canConnectRedstone(side: Int): Boolean = true |
| |
| override def strongPowerLevel(side: Int): Int = weakPowerLevel(side) |
| |
| override def weakPowerLevel(side: Int): Int = if (data.emitRedstone(state)) data.redstoneLevel else 0 |
| |
| // ----------------------------------------------------------------------- // |
| |
| override def activate(player: EntityPlayer, hit: MovingObjectPosition, item: ItemStack): Boolean = { |
| if (data.hasActiveState) { |
| if (!state || !data.isButtonMode) { |
| toggleState() |
| return true |
| } |
| } |
| false |
| } |
| |
| def toggleState(): Unit = { |
| if (canToggle) { |
| toggling = true |
| state = !state |
| // Update slot info in tile... kinda meh, but works. |
| tile match { |
| case slotted: TSlottedTile => |
| for (i <- slotted.v_partMap.indices) { |
| if (slotted.v_partMap(i) == this) |
| slotted.v_partMap(i) = null |
| } |
| tile.bindPart(this) |
| case _ => |
| } |
| world.playSoundEffect(x + 0.5, y + 0.5, z + 0.5, "random.click", 0.3F, if (state) 0.6F else 0.5F) |
| tile.notifyPartChange(this) |
| sendDescUpdate() |
| if (state && data.isButtonMode) { |
| scheduleTick(simpleBlock.tickRate(world)) |
| } |
| toggling = false |
| } |
| } |
| |
| def canToggle = !toggling && world != null && !world.isRemote && { |
| val toggled = new PrintPart() |
| toggled.facing = facing |
| toggled.data = data |
| toggled.state = !state |
| toggled.boundsOff = boundsOff |
| toggled.boundsOn = boundsOn |
| tile.canReplacePart(this, toggled) |
| } |
| |
| // ----------------------------------------------------------------------- // |
| |
| override def scheduledTick(): Unit = if (state) toggleState() |
| |
| override def pickItem(hit: MovingObjectPosition): ItemStack = data.createItemStack() |
| |
| override def getDrops: lang.Iterable[ItemStack] = asJavaIterable(Iterable(data.createItemStack())) |
| |
| override def collisionRayTrace(start: Vec3, end: Vec3): ExtendedMOP = { |
| val shapes = if (state) data.stateOn else data.stateOff |
| var closestDistance = Double.PositiveInfinity |
| var closest: Option[MovingObjectPosition] = None |
| for (shape <- shapes) { |
| val bounds = shape.bounds.rotateTowards(facing).offset(x, y, z) |
| val hit = bounds.calculateIntercept(start, end) |
| if (hit != null) { |
| val distance = hit.hitVec.distanceTo(start) |
| if (distance < closestDistance) { |
| closestDistance = distance |
| hit.blockX = x |
| hit.blockY = y |
| hit.blockZ = z |
| closest = Option(hit) |
| } |
| } |
| } |
| closest.fold(if (shapes.isEmpty) new ExtendedMOP(x, y, z, 0, Vec3.createVectorHelper(0.5, 0.5, 0.5), null) else null)(hit => new ExtendedMOP(hit, null, closestDistance)) |
| } |
| |
| @SideOnly(Side.CLIENT) |
| override def drawHighlight(hit: MovingObjectPosition, player: EntityPlayer, frame: Float): Boolean = { |
| val pos = player.getPosition(frame) |
| val expansion = 0.002f |
| |
| // See RenderGlobal.drawSelectionBox. |
| GL11.glEnable(GL11.GL_BLEND) |
| OpenGlHelper.glBlendFunc(770, 771, 1, 0) |
| GL11.glColor4f(0, 0, 0, 0.4f) |
| GL11.glLineWidth(2) |
| GL11.glDisable(GL11.GL_TEXTURE_2D) |
| GL11.glDepthMask(false) |
| |
| for (shape <- if (state) data.stateOn else data.stateOff) { |
| val bounds = shape.bounds.rotateTowards(facing) |
| RenderGlobal.drawOutlinedBoundingBox(bounds.copy().expand(expansion, expansion, expansion) |
| .offset(hit.blockX, hit.blockY, hit.blockZ) |
| .offset(-pos.xCoord, -pos.yCoord, -pos.zCoord), -1) |
| } |
| |
| GL11.glDepthMask(true) |
| GL11.glEnable(GL11.GL_TEXTURE_2D) |
| GL11.glDisable(GL11.GL_BLEND) |
| |
| true |
| } |
| |
| override def onPartChanged(part: TMultiPart): Unit = { |
| super.onPartChanged(part) |
| checkRedstone() |
| } |
| |
| override def onNeighborChanged(): Unit = { |
| super.onNeighborChanged() |
| checkRedstone() |
| } |
| |
| protected def checkRedstone(): Unit = { |
| val newMaxValue = computeInput() |
| val newState = newMaxValue > 1 // >1 Fixes oddities in cycling updates. |
| if (!data.emitRedstone && data.hasActiveState && state != newState) { |
| toggleState() |
| } |
| } |
| |
| protected def computeInput(): Int = { |
| val inner = tile.partList.foldLeft(0)((power, part) => part match { |
| case print: PrintPart if print.data.emitRedstone(print.state) => math.max(power, print.data.redstoneLevel) |
| case _ => power |
| }) |
| math.max(inner, ForgeDirection.VALID_DIRECTIONS.map(BundledRedstone.computeInput(BlockPosition(x, y, z, world), _)).max) |
| } |
| |
| // ----------------------------------------------------------------------- // |
| |
| override def load(nbt: NBTTagCompound) { |
| super.load(nbt) |
| facing = nbt.getDirection("facing").getOrElse(facing) |
| data.load(nbt.getCompoundTag("data")) |
| state = nbt.getBoolean("state") |
| updateBounds() |
| } |
| |
| override def save(nbt: NBTTagCompound) { |
| super.save(nbt) |
| nbt.setDirection("facing", Option(facing)) |
| nbt.setNewCompoundTag("data", data.save) |
| nbt.setBoolean("state", state) |
| } |
| |
| override def readDesc(packet: MCDataInput) { |
| super.readDesc(packet) |
| facing = ForgeDirection.getOrientation(packet.readUByte()) |
| data.load(packet.readNBTTagCompound()) |
| state = packet.readBoolean() |
| updateBounds() |
| if (world != null) { |
| world.markBlockForUpdate(x, y, z) |
| } |
| } |
| |
| override def writeDesc(packet: MCDataOutput) { |
| super.writeDesc(packet) |
| packet.writeByte(facing.ordinal().toByte) |
| val nbt = new NBTTagCompound() |
| data.save(nbt) |
| packet.writeNBTTagCompound(nbt) |
| packet.writeBoolean(state) |
| } |
| |
| def updateBounds(): Unit = { |
| boundsOff = data.stateOff.drop(1).foldLeft(data.stateOff.headOption.fold(ExtendedAABB.unitBounds)(_.bounds))((a, b) => a.func_111270_a(b.bounds)) |
| if (boundsOff.volume == 0) boundsOff = ExtendedAABB.unitBounds |
| else boundsOff = boundsOff.rotateTowards(facing) |
| boundsOn = data.stateOn.drop(1).foldLeft(data.stateOn.headOption.fold(ExtendedAABB.unitBounds)(_.bounds))((a, b) => a.func_111270_a(b.bounds)) |
| if (boundsOn.volume == 0) boundsOn = ExtendedAABB.unitBounds |
| else boundsOn = boundsOn.rotateTowards(facing) |
| } |
| |
| // ----------------------------------------------------------------------- // |
| |
| @SideOnly(Side.CLIENT) |
| override def renderStatic(pos: Vector3, pass: Int) = { |
| val (x, y, z) = (pos.x.toInt, pos.y.toInt, pos.z.toInt) |
| val renderer = RenderBlocks.getInstance |
| renderer.blockAccess = world |
| Print.render(data, state, facing, x, y, z, simpleBlock, renderer) |
| true |
| } |
| |
| } |