blob: db89c170fe9c1aac614b3a3a9809045606aaf7ae [file] [log] [blame] [raw]
package li.cil.oc.common.tileentity
import cpw.mods.fml.relauncher.{Side, SideOnly}
import li.cil.oc.api.network._
import li.cil.oc.server.{PacketSender => ServerPacketSender}
import li.cil.oc.{Settings, api}
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.nbt.NBTTagCompound
import net.minecraft.util.AxisAlignedBB
import net.minecraftforge.common.ForgeDirection
class Hologram extends traits.Environment with SidedEnvironment with Analyzable {
val node = api.Network.newNode(this, Visibility.Network).
withComponent("hologram").
withConnector().
create()
val width = 3 * 16
val height = 2 * 16 // 32 bit in an int
val volume = new Array[Int](width * width)
// Render scale.
var scale = 1.0
// Relative number of lit columns (for energy cost).
var litRatio = -1.0
// Whether we need to send an update packet/recompile our display list.
var dirty = false
// Interval of dirty columns.
var dirtyFromX = Int.MaxValue
var dirtyUntilX = -1
var dirtyFromZ = Int.MaxValue
var dirtyUntilZ = -1
// Time to wait before sending another update packet.
var cooldown = 5
var hasPower = true
def setDirty(x: Int, z: Int) {
dirty = true
dirtyFromX = math.min(dirtyFromX, x)
dirtyUntilX = math.max(dirtyUntilX, x + 1)
dirtyFromZ = math.min(dirtyFromZ, z)
dirtyUntilZ = math.max(dirtyUntilZ, z + 1)
litRatio = -1
}
def resetDirtyFlag() {
dirty = false
dirtyFromX = Int.MaxValue
dirtyUntilX = -1
dirtyFromZ = Int.MaxValue
dirtyUntilZ = -1
cooldown = 5
}
// ----------------------------------------------------------------------- //
@SideOnly(Side.CLIENT)
override def canConnect(side: ForgeDirection) = side == ForgeDirection.DOWN
override def sidedNode(side: ForgeDirection) = if (side == ForgeDirection.DOWN) node else null
// Override automatic analyzer implementation for sided environments.
override def onAnalyze(player: EntityPlayer, side: Int, hitX: Float, hitY: Float, hitZ: Float) = Array(node)
// ----------------------------------------------------------------------- //
@Callback(doc = """function() -- Clears the hologram.""")
def clear(computer: Context, args: Arguments): Array[AnyRef] = this.synchronized {
for (i <- 0 until volume.length) volume(i) = 0
ServerPacketSender.sendHologramClear(this)
resetDirtyFlag()
litRatio = 0
null
}
@Callback(direct = true, doc = """function(x:number, z:number):number -- Returns the bit mask representing the specified column.""")
def get(computer: Context, args: Arguments): Array[AnyRef] = this.synchronized {
val x = args.checkInteger(0) - 1
if (x < 0 || x >= width) throw new ArrayIndexOutOfBoundsException()
val z = args.checkInteger(1) - 1
if (z < 0 || z >= width) throw new ArrayIndexOutOfBoundsException()
result(volume(x + z * width))
}
@Callback(direct = true, limit = 256, doc = """function(x:number, z:number, value:number) -- Set the bit mask for the specified column.""")
def set(computer: Context, args: Arguments): Array[AnyRef] = this.synchronized {
val x = args.checkInteger(0) - 1
if (x < 0 || x >= width) throw new ArrayIndexOutOfBoundsException()
val z = args.checkInteger(1) - 1
if (z < 0 || z >= width) throw new ArrayIndexOutOfBoundsException()
val value = args.checkDouble(2).longValue.intValue
volume(x + z * width) = value
setDirty(x, z)
null
}
@Callback(direct = true, limit = 128, doc = """function(x:number, z:number, height:number) -- Fills a column to the specified height.""")
def fill(computer: Context, args: Arguments): Array[AnyRef] = this.synchronized {
val x = args.checkInteger(0) - 1
if (x < 0 || x >= width) throw new ArrayIndexOutOfBoundsException()
val z = args.checkInteger(1) - 1
if (z < 0 || z >= width) throw new ArrayIndexOutOfBoundsException()
val height = math.min(32, math.max(0, args.checkInteger(2)))
// Bit shifts in the JVM only use the lowest five bits... so we have to
// manually check the height, to avoid the shift being a no-op.
volume(x + z * width) = if (height > 0) 0xFFFFFFFF >>> (32 - height) else 0
setDirty(x, z)
null
}
@Callback(doc = """function():number -- Returns the render scale of the hologram.""")
def getScale(computer: Context, args: Arguments): Array[AnyRef] = {
result(scale)
}
@Callback(doc = """function(value:number) -- Set the render scale. A larger scale consumes more energy.""")
def setScale(computer: Context, args: Arguments): Array[AnyRef] = {
scale = math.max(0.333333, math.min(3, args.checkDouble(0)))
ServerPacketSender.sendHologramScale(this)
null
}
// ----------------------------------------------------------------------- //
override def updateEntity() {
super.updateEntity()
if (isServer) {
if (dirty) {
cooldown -= 1
if (cooldown <= 0) this.synchronized {
ServerPacketSender.sendHologramSet(this)
resetDirtyFlag()
}
}
if (world.getWorldTime % Settings.get.tickFrequency == 0) {
if (litRatio < 0) this.synchronized {
litRatio = 0
for (i <- 0 until volume.length) {
if (volume(i) != 0) litRatio += 1
}
litRatio /= volume.length
}
val hadPower = hasPower
val neededPower = Settings.get.hologramCost * litRatio * scale * Settings.get.tickFrequency
hasPower = node.tryChangeBuffer(-neededPower)
if (hasPower != hadPower) {
ServerPacketSender.sendHologramPowerChange(this)
}
}
}
}
// ----------------------------------------------------------------------- //
override def shouldRenderInPass(pass: Int) = pass == 1
override def getRenderBoundingBox = AxisAlignedBB.getAABBPool.getAABB(xCoord + 0.5 - 1.5 * scale, yCoord, zCoord - scale, xCoord + 0.5 + 1.5 * scale, yCoord + 0.25 + 2 * scale, zCoord + 0.5 + 1.5 * scale)
// ----------------------------------------------------------------------- //
override def readFromNBT(nbt: NBTTagCompound) {
super.readFromNBT(nbt)
nbt.getIntArray(Settings.namespace + "volume").copyToArray(volume)
scale = nbt.getDouble(Settings.namespace + "scale")
}
override def writeToNBT(nbt: NBTTagCompound) = this.synchronized {
super.writeToNBT(nbt)
nbt.setIntArray(Settings.namespace + "volume", volume)
nbt.setDouble(Settings.namespace + "scale", scale)
}
@SideOnly(Side.CLIENT)
override def readFromNBTForClient(nbt: NBTTagCompound) {
super.readFromNBTForClient(nbt)
nbt.getIntArray("volume").copyToArray(volume)
scale = nbt.getDouble("scale")
hasPower = nbt.getBoolean("hasPower")
dirty = true
}
override def writeToNBTForClient(nbt: NBTTagCompound) {
super.writeToNBTForClient(nbt)
nbt.setIntArray("volume", volume)
nbt.setDouble("scale", scale)
nbt.setBoolean("hasPower", hasPower)
}
}