mirror of
https://github.com/GTNewHorizons/ForgeMultipart.git
synced 2026-11-13 17:04:25 +00:00
656 lines
18 KiB
Scala
656 lines
18 KiB
Scala
package codechicken.multipart
|
|
|
|
import net.minecraft.tileentity.TileEntity
|
|
import scala.collection.mutable.ListBuffer
|
|
import net.minecraft.network.packet.Packet
|
|
import codechicken.core.packet.PacketCustom
|
|
import codechicken.multipart.handler.MultipartCPH
|
|
import codechicken.core.vec.BlockCoord
|
|
import net.minecraft.world.World
|
|
import java.util.List
|
|
import scala.collection.JavaConverters._
|
|
import net.minecraft.nbt.NBTTagCompound
|
|
import java.io.ByteArrayOutputStream
|
|
import java.io.DataOutputStream
|
|
import java.io.DataInputStream
|
|
import codechicken.core.data.MCDataInputStream
|
|
import codechicken.core.data.MCDataOutput
|
|
import codechicken.core.data.MCDataOutputStream
|
|
import net.minecraft.client.multiplayer.NetClientHandler
|
|
import codechicken.core.vec.Cuboid6
|
|
import scala.collection.mutable.HashSet
|
|
import codechicken.multipart.handler.MultipartProxy
|
|
import net.minecraft.block.Block
|
|
import net.minecraft.item.ItemStack
|
|
import codechicken.core.vec.Vector3
|
|
import net.minecraft.nbt.NBTTagList
|
|
import net.minecraft.client.particle.EffectRenderer
|
|
import net.minecraft.util.MovingObjectPosition
|
|
import scala.collection.mutable.ArrayBuffer
|
|
import codechicken.scala.ScalaBridge._
|
|
import java.util.Random
|
|
import cpw.mods.fml.relauncher.SideOnly
|
|
import cpw.mods.fml.relauncher.Side
|
|
import scala.collection.mutable.Queue
|
|
import codechicken.multipart.handler.MultipartSPH
|
|
import codechicken.core.lighting.LazyLightMatrix
|
|
import net.minecraft.world.ChunkCoordIntPair
|
|
import net.minecraft.entity.item.EntityItem
|
|
import net.minecraft.entity.player.EntityPlayer
|
|
import net.minecraft.entity.Entity
|
|
|
|
trait TileMultipart extends TileEntity
|
|
{
|
|
var partList = ArrayBuffer[TMultiPart]()
|
|
|
|
private var doesTick = false
|
|
|
|
private[multipart] def from(that:TileMultipart)
|
|
{
|
|
copyFrom(that)
|
|
loadFrom(that)
|
|
}
|
|
|
|
def copyFrom(that:TileMultipart)//Potentially auto gen this
|
|
{
|
|
partList = that.partList
|
|
doesTick = that.doesTick
|
|
}
|
|
|
|
def loadFrom(that:TileMultipart)
|
|
{
|
|
partList.foreach(_.bind(this))
|
|
}
|
|
|
|
/**
|
|
* Overidden in TSlottedTile when a part that goes in a slot is added
|
|
*/
|
|
def partMap(slot:Int):TMultiPart = null
|
|
|
|
def jPartList():List[TMultiPart] = partList.asJava
|
|
|
|
override def canUpdate() = doesTick//TODO: part merging true
|
|
|
|
override def updateEntity()
|
|
{
|
|
super.updateEntity()
|
|
|
|
TileMultipartObj.startOperation(this)
|
|
partList.foreach(_.update())
|
|
TileMultipartObj.finishOperation(this)
|
|
}
|
|
|
|
override def onChunkUnload()
|
|
{
|
|
partList.foreach(_.onChunkUnload())
|
|
}
|
|
|
|
def onChunkLoad()
|
|
{
|
|
partList.foreach(_.onChunkLoad())
|
|
}
|
|
|
|
override def validate()
|
|
{
|
|
val wasInvalid = isInvalid()
|
|
super.validate()
|
|
if(wasInvalid)
|
|
partList.foreach(_.onWorldJoin())
|
|
}
|
|
|
|
override def invalidate()
|
|
{
|
|
super.invalidate()
|
|
if(worldObj != null)
|
|
partList.foreach(_.onWorldSeparate())
|
|
}
|
|
|
|
def notifyPartChange()
|
|
{
|
|
TileMultipartObj.startOperation(this)
|
|
partList.foreach(_.onPartChanged())
|
|
TileMultipartObj.finishOperation(this)
|
|
|
|
worldObj.notifyBlocksOfNeighborChange(xCoord, yCoord, zCoord, getBlockType().blockID)
|
|
worldObj.updateAllLightTypes(xCoord, yCoord, zCoord)
|
|
}
|
|
|
|
def onNeighborBlockChange(world:World, x:Int, y:Int, z:Int, id:Int)
|
|
{
|
|
TileMultipartObj.startOperation(this)
|
|
partList.foreach(_.onNeighborChanged())
|
|
TileMultipartObj.finishOperation(this)
|
|
}
|
|
|
|
def getLightValue() = partList.foldLeft(0)((l, p) => Math.max(l, p.getLightValue))
|
|
|
|
def markDirty()
|
|
{
|
|
worldObj.updateTileEntityChunkAndDoNothing(xCoord, yCoord, zCoord, this)
|
|
}
|
|
|
|
def isSolid(side:Int):Boolean =
|
|
{
|
|
val part = partMap(side)
|
|
if(part != null)
|
|
return part.asInstanceOf[TFacePart].solid(side)
|
|
|
|
return false
|
|
}
|
|
|
|
private def setTicking(tick:Boolean)
|
|
{
|
|
if(doesTick == tick)
|
|
return
|
|
|
|
doesTick = tick
|
|
if(worldObj != null)
|
|
{
|
|
if(tick)
|
|
worldObj.addTileEntity(this)
|
|
else
|
|
worldObj.loadedTileEntityList.remove(this)
|
|
}
|
|
}
|
|
|
|
def canAddPart(part:TMultiPart):Boolean =
|
|
{
|
|
if(partList.contains(part))
|
|
return false
|
|
|
|
return occlusionTest(partList, part)
|
|
}
|
|
|
|
def canReplacePart(opart:TMultiPart, npart:TMultiPart):Boolean =
|
|
{
|
|
val olist = partList.filterNot(_ == opart)
|
|
if(olist.contains(npart))
|
|
return false
|
|
|
|
return occlusionTest(olist, npart)
|
|
}
|
|
|
|
def occlusionTest(parts:Seq[TMultiPart], npart:TMultiPart):Boolean =
|
|
{
|
|
return parts.forall(part => part.occlusionTest(npart) && npart.occlusionTest(part))
|
|
}
|
|
|
|
def getWriteStream(part:TMultiPart):MCDataOutput = getWriteStream.writeByte(partList.indexOf(part))
|
|
|
|
private[multipart] def getWriteStream = MultipartSPH.getTileStream(worldObj, new BlockCoord(this))
|
|
|
|
private[multipart] def addPart(part:TMultiPart)
|
|
{
|
|
if(!worldObj.isRemote)
|
|
writeAddPart(part)
|
|
|
|
addPart_do(part)
|
|
part.onAdded()
|
|
notifyPartChange()
|
|
markDirty()
|
|
markRender()
|
|
}
|
|
|
|
private[multipart] def writeAddPart(part:TMultiPart)
|
|
{
|
|
val stream = getWriteStream.writeByte(253)
|
|
MultiPartRegistry.writePartID(stream, part)
|
|
part.writeDesc(stream)
|
|
}
|
|
|
|
private[multipart] def addPart_do(part:TMultiPart)
|
|
{
|
|
if(partList.size >= 250)
|
|
throw new IllegalArgumentException("Tried to add more than 250 parts to the one tile. You're doing it wrong")
|
|
|
|
partList+=part
|
|
bindPart(part)
|
|
part.bind(this)
|
|
|
|
if(!doesTick && part.doesTick)
|
|
setTicking(true)
|
|
|
|
partAdded(part)
|
|
}
|
|
|
|
/**
|
|
* Bind this part to an internal cache
|
|
*/
|
|
def bindPart(part:TMultiPart){}
|
|
|
|
def partAdded(part:TMultiPart){}
|
|
|
|
def remPart(part:TMultiPart):TileMultipart =
|
|
{
|
|
if(TileMultipartObj.queueRemoval(this, part))
|
|
return null
|
|
|
|
val i = remPart_do(part)
|
|
if(!isInvalid())
|
|
{
|
|
notifyPartChange()
|
|
markDirty()
|
|
markRender()
|
|
}
|
|
|
|
if(!worldObj.isRemote)
|
|
getWriteStream.writeByte(254).writeByte(i)
|
|
|
|
if(!isInvalid())
|
|
return MultipartGenerator.partRemoved(this, part)
|
|
|
|
return null
|
|
}
|
|
|
|
private def remPart_do(part:TMultiPart):Int =
|
|
{
|
|
val r = partList.indexOf(part)
|
|
if(r < 0)
|
|
throw new IllegalArgumentException("Tried to remove a non-existant part")
|
|
|
|
partList-=part
|
|
|
|
partRemoved(part, r)
|
|
part.onRemoved()
|
|
|
|
if(partList.isEmpty)
|
|
{
|
|
worldObj.setBlockToAir(xCoord, yCoord, zCoord)
|
|
}
|
|
else
|
|
{
|
|
if(part.doesTick && doesTick)
|
|
{
|
|
var ntick = false
|
|
partList.foreach(part => ntick |= part.doesTick)
|
|
if(!ntick)
|
|
setTicking(false)
|
|
}
|
|
}
|
|
return r
|
|
}
|
|
|
|
def partRemoved(part:TMultiPart, p:Int){}
|
|
|
|
private[multipart] def loadParts(parts:ListBuffer[TMultiPart])
|
|
{
|
|
clearParts()
|
|
parts.foreach(p => addPart_do(p))
|
|
if(worldObj != null)
|
|
notifyPartChange()
|
|
}
|
|
|
|
def clearParts()
|
|
{
|
|
partList.clear()
|
|
}
|
|
|
|
def writeDesc(packet:PacketCustom)
|
|
{
|
|
packet.writeByte(partList.size)
|
|
partList.foreach{part =>
|
|
MultiPartRegistry.writePartID(packet, part)
|
|
part.writeDesc(packet)
|
|
}
|
|
}
|
|
|
|
def harvestPart(index:Int, drop:Boolean):Boolean =
|
|
{
|
|
val part = partList(index)
|
|
if(part == null)
|
|
return false
|
|
if(drop)
|
|
dropItems(part.getDrops)
|
|
remPart(part)
|
|
return true
|
|
}
|
|
|
|
def dropItems(items:Seq[ItemStack])
|
|
{
|
|
val pos = Vector3.fromTileEntityCenter(this)
|
|
items.foreach(item => TileMultipartObj.dropItem(item, worldObj, pos))
|
|
}
|
|
|
|
def markRender()
|
|
{
|
|
worldObj.markBlockForRenderUpdate(xCoord, yCoord, zCoord)
|
|
}
|
|
|
|
override def writeToNBT(tag:NBTTagCompound)
|
|
{
|
|
super.writeToNBT(tag)
|
|
val taglist = new NBTTagList
|
|
partList.foreach{part =>
|
|
val parttag = new NBTTagCompound
|
|
parttag.setString("id", part.getType)
|
|
part.save(parttag)
|
|
taglist.appendTag(parttag)
|
|
}
|
|
tag.setTag("parts", taglist)
|
|
}
|
|
|
|
def onEntityCollision(entity:Entity)
|
|
{
|
|
TileMultipartObj.startOperation(this)
|
|
partList.foreach(_.onEntityCollision(entity))
|
|
TileMultipartObj.finishOperation(this)
|
|
}
|
|
|
|
def strongPowerLevel(side:Int) = 0
|
|
|
|
def weakPowerLevel(side:Int) = 0
|
|
|
|
def canConnectRedstone(side:Int) = false
|
|
|
|
def notifyNeighborChange(side:Int)
|
|
{
|
|
val pos = new BlockCoord(this).offset(side)
|
|
worldObj.notifyBlocksOfNeighborChange(pos.x, pos.y, pos.z, getBlockType().blockID)
|
|
}
|
|
}
|
|
|
|
trait TileMultipartClient extends TileMultipart
|
|
{
|
|
def renderStatic(pos:Vector3, olm:LazyLightMatrix, pass:Int)
|
|
{
|
|
partList.foreach(part => part.renderStatic(pos, olm, pass))
|
|
}
|
|
|
|
def renderDynamic(pos:Vector3, frame:Float)
|
|
{
|
|
partList.foreach(part => part.renderDynamic(pos, frame))
|
|
}
|
|
|
|
def randomDisplayTick(random:Random){}
|
|
}
|
|
|
|
object TileMultipartObj
|
|
{
|
|
var renderID:Int = -1
|
|
|
|
class TileOperationSet
|
|
{
|
|
var tile:TileMultipart = _
|
|
var depth = 0
|
|
private val removalQueue = Queue[TMultiPart]()
|
|
private val additionQueue = Queue[TMultiPart]()
|
|
|
|
def start(t:TileMultipart)
|
|
{
|
|
tile = t
|
|
depth = 1
|
|
}
|
|
|
|
def queueAddition(part:TMultiPart)
|
|
{
|
|
if(!additionQueue.contains(part))
|
|
additionQueue+=part
|
|
}
|
|
|
|
def queueRemoval(part:TMultiPart)
|
|
{
|
|
if(!removalQueue.contains(part))
|
|
removalQueue+=part
|
|
}
|
|
|
|
def finish()
|
|
{
|
|
if(removalQueue.isEmpty && additionQueue.isEmpty)
|
|
return
|
|
|
|
var otile = tile
|
|
val world = tile.worldObj
|
|
val pos = new BlockCoord(tile)
|
|
while(!removalQueue.isEmpty)
|
|
otile = otile.remPart(removalQueue.dequeue)
|
|
|
|
while(!additionQueue.isEmpty)
|
|
MultipartGenerator.addPart(world, pos, additionQueue.dequeue)
|
|
}
|
|
}
|
|
|
|
class OperationSynchroniser
|
|
{
|
|
private val operations = ArrayBuffer[TileOperationSet]()
|
|
private var depth = 0
|
|
|
|
def startOperation(tile:TileMultipart)
|
|
{
|
|
var i = 0
|
|
while(i < depth)
|
|
{
|
|
val op = operations(i)
|
|
if(op.tile == tile)
|
|
{
|
|
op.depth+=1
|
|
return
|
|
}
|
|
i+=1
|
|
}
|
|
if(depth == operations.length)
|
|
operations+=new TileOperationSet
|
|
operations(depth).start(tile)
|
|
depth+=1
|
|
}
|
|
|
|
def finishOperation(tile:TileMultipart)
|
|
{
|
|
var i = depth-1
|
|
while(i >= 0)
|
|
{
|
|
val op = operations(i)
|
|
if(op.tile == tile)
|
|
{
|
|
op.depth-=1
|
|
if(op.depth == 0)
|
|
{
|
|
if(i != depth-1)
|
|
throw new IllegalStateException("Tried to finish an operation that was not on top")
|
|
depth-=1
|
|
op.finish()
|
|
}
|
|
return
|
|
}
|
|
i-=1
|
|
}
|
|
throw new IllegalStateException("Inconsistant Operation stack")
|
|
}
|
|
|
|
def queueRemoval(tile:TileMultipart, part:TMultiPart):Boolean =
|
|
{
|
|
var i = depth-1
|
|
while(i >= 0)
|
|
{
|
|
val op = operations(i)
|
|
if(op.tile == tile)
|
|
{
|
|
op.queueRemoval(part)
|
|
return true
|
|
}
|
|
i-=1
|
|
}
|
|
return false
|
|
}
|
|
|
|
def queueAddition(world:World, pos:BlockCoord, part:TMultiPart):Boolean =
|
|
{
|
|
var i = depth-1
|
|
while(i >= 0)
|
|
{
|
|
val op = operations(i)
|
|
val opt = op.tile
|
|
if(opt.worldObj == world && opt.xCoord == pos.x && opt.yCoord == pos.y && opt.zCoord == pos.z)
|
|
{
|
|
op.queueRemoval(part)
|
|
return true
|
|
}
|
|
i-=1
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
|
|
private var operationSync_ = new ThreadLocal[OperationSynchroniser]
|
|
private def operationSync():OperationSynchroniser =
|
|
{
|
|
var r = operationSync_.get
|
|
if(r == null)
|
|
{
|
|
r = new OperationSynchroniser
|
|
operationSync_.set(r)
|
|
}
|
|
return r
|
|
}
|
|
|
|
def startOperation(tile:TileMultipart) = operationSync.startOperation(tile)
|
|
|
|
def finishOperation(tile:TileMultipart) = operationSync.finishOperation(tile)
|
|
|
|
def queueRemoval(tile:TileMultipart, part:TMultiPart):Boolean = operationSync.queueRemoval(tile, part)
|
|
|
|
def queueAddition(world:World, pos:BlockCoord, part:TMultiPart):Boolean = operationSync.queueAddition(world, pos, part)
|
|
|
|
def getOrConvertTile(world:World, pos:BlockCoord) = getOrConvertTile2(world, pos)._1
|
|
|
|
def getOrConvertTile2(world:World, pos:BlockCoord):(TileMultipart, Boolean) =
|
|
{
|
|
val t = world.getBlockTileEntity(pos.x, pos.y, pos.z)
|
|
if(t.isInstanceOf[TileMultipart])
|
|
return (t.asInstanceOf[TileMultipart], false)
|
|
|
|
val id = world.getBlockId(pos.x, pos.y, pos.z)
|
|
val p = MultiPartRegistry.convertBlock(world, pos, id)
|
|
if(p != null)
|
|
{
|
|
val t = MultipartGenerator.generateCompositeTile(null, Seq(p), world.isRemote)
|
|
t.xCoord = pos.x
|
|
t.yCoord = pos.y
|
|
t.zCoord = pos.z
|
|
t.setWorldObj(world)
|
|
t.addPart_do(p)
|
|
return (t, true)
|
|
}
|
|
return (null, false)
|
|
}
|
|
|
|
def getTile(world:World, pos:BlockCoord):TileMultipart =
|
|
{
|
|
val t = world.getBlockTileEntity(pos.x, pos.y, pos.z)
|
|
if(t.isInstanceOf[TileMultipart])
|
|
return t.asInstanceOf[TileMultipart]
|
|
return null
|
|
}
|
|
|
|
def canAddPart(world:World, pos:BlockCoord, part:TMultiPart):Boolean =
|
|
{
|
|
part.getCollisionBoxes.foreach{b =>
|
|
if(!world.checkNoEntityCollision(b.toAABB().offset(pos.x, pos.y, pos.z)))
|
|
return false
|
|
}
|
|
|
|
val t = getOrConvertTile(world, pos)
|
|
if(t != null)
|
|
return t.canAddPart(part)
|
|
|
|
if(!replaceable(world, pos))
|
|
return false
|
|
|
|
return true
|
|
}
|
|
|
|
def replaceable(world:World, pos:BlockCoord):Boolean =
|
|
{
|
|
val block = Block.blocksList(world.getBlockId(pos.x, pos.y, pos.z))
|
|
return block == null || block.isAirBlock(world, pos.x, pos.y, pos.z) || block.isBlockReplaceable(world, pos.x, pos.y, pos.z)
|
|
}
|
|
|
|
def addPart(world:World, pos:BlockCoord, part:TMultiPart):TileMultipart =
|
|
{
|
|
if(queueAddition(world, pos, part))
|
|
return null
|
|
|
|
return MultipartGenerator.addPart(world, pos, part)
|
|
}
|
|
|
|
def handleDescPacket(world:World, pos:BlockCoord, packet:PacketCustom)
|
|
{
|
|
val nparts = packet.readUnsignedByte
|
|
val parts = new ListBuffer[TMultiPart]()
|
|
for(i <- 0 until nparts)
|
|
{
|
|
val part:TMultiPart = MultiPartRegistry.readPart(packet)
|
|
part.readDesc(packet)
|
|
parts+=part
|
|
}
|
|
|
|
if(parts.size == 0)
|
|
return
|
|
|
|
val t = world.getBlockTileEntity(pos.x, pos.y, pos.z)
|
|
val tilemp = MultipartGenerator.generateCompositeTile(t, parts, true)
|
|
if(tilemp != t)
|
|
world.setBlockTileEntity(pos.x, pos.y, pos.z, tilemp)
|
|
|
|
tilemp.loadParts(parts)
|
|
tilemp.markRender()
|
|
}
|
|
|
|
def handlePacket(pos:BlockCoord, world:World, i:Int, packet:PacketCustom)
|
|
{
|
|
var tilemp = BlockMultipart.getTile(world, pos.x, pos.y, pos.z)
|
|
|
|
i match
|
|
{
|
|
case 253 => {
|
|
val part = MultiPartRegistry.readPart(packet)
|
|
part.readDesc(packet)
|
|
addPart(world, pos, part)
|
|
}
|
|
case 254 => if(tilemp != null) {
|
|
tilemp.remPart(tilemp.partList(packet.readUnsignedByte()))
|
|
}
|
|
case _ => if(tilemp != null) {
|
|
tilemp.partList(i).read(packet)
|
|
}
|
|
}
|
|
}
|
|
|
|
def createFromNBT(tag:NBTTagCompound):TileMultipart =
|
|
{
|
|
val superID = tag.getString("superID")
|
|
//val superClass = TileEntity.nameToClassMap.get(superID)
|
|
|
|
val partList = tag.getTagList("parts")
|
|
val parts = ListBuffer[TMultiPart]()
|
|
|
|
for(i <- 0 until partList.tagCount)
|
|
{
|
|
val partTag = partList.tagAt(i).asInstanceOf[NBTTagCompound]
|
|
val partID = partTag.getString("id")
|
|
val part = MultiPartRegistry.createPart(partID, false)
|
|
if(part != null)
|
|
{
|
|
part.load(partTag)
|
|
parts+=part
|
|
}
|
|
}
|
|
|
|
if(parts.size == 0)
|
|
return null
|
|
|
|
val tmb = MultipartGenerator.generateCompositeTile(null, parts, false)
|
|
tmb.readFromNBT(tag)
|
|
tmb.loadParts(parts)
|
|
return tmb
|
|
}
|
|
|
|
def dropItem(stack:ItemStack, world:World, pos:Vector3)
|
|
{
|
|
val item = new EntityItem(world, pos.x, pos.y, pos.z, stack);
|
|
item.motionX = world.rand.nextGaussian() * 0.05;
|
|
item.motionY = world.rand.nextGaussian() * 0.05 + 0.2F;
|
|
item.motionZ = world.rand.nextGaussian() * 0.05;
|
|
world.spawnEntityInWorld(item);
|
|
}
|
|
}
|