Maybe fix build? Also temporarily adding FMP lib

This commit is contained in:
Aidan Brady 2014-01-04 13:16:24 -05:00
parent 36c6b8cab5
commit 4f68913eba
57 changed files with 7239 additions and 0 deletions

View file

@ -0,0 +1,311 @@
package codechicken.multipart
import java.util.List
import java.lang.Iterable
import net.minecraft.block.Block
import net.minecraft.block.material.Material
import net.minecraft.entity.Entity
import net.minecraft.util.AxisAlignedBB
import net.minecraft.world.World
import net.minecraftforge.common.ForgeDirection
import net.minecraft.util.Vec3
import net.minecraft.util.MovingObjectPosition
import codechicken.lib.raytracer.RayTracer
import net.minecraft.entity.player.EntityPlayer
import java.util.Random
import java.util.ArrayList
import net.minecraft.item.ItemStack
import net.minecraft.client.particle.EffectRenderer
import net.minecraft.client.Minecraft
import net.minecraft.client.renderer.texture.IconRegister
import codechicken.lib.render.TextureUtils
import net.minecraft.world.IBlockAccess
import codechicken.lib.raytracer.ExtendedMOP
import scala.collection.JavaConversions._
object BlockMultipart
{
def getTile(world:IBlockAccess, x:Int, y:Int, z:Int):TileMultipart =
{
val tile = world.getBlockTileEntity(x, y, z)
if(tile.isInstanceOf[TileMultipart])
tile.asInstanceOf[TileMultipart]
else
null
}
def getClientTile(world:IBlockAccess, x:Int, y:Int, z:Int):TileMultipartClient =
{
val tile = world.getBlockTileEntity(x, y, z)
if(tile.isInstanceOf[TileMultipartClient])
tile.asInstanceOf[TileMultipartClient]
else
null
}
def reduceMOP(hit:MovingObjectPosition):(Int, ExtendedMOP) = {
val ehit = hit.asInstanceOf[ExtendedMOP]
val data:(Int, _) = ExtendedMOP.getData(hit)
return (data._1, new ExtendedMOP(ehit, data._2, ehit.dist))
}
def drawHighlight(world:World, player:EntityPlayer, hit:MovingObjectPosition, frame:Float):Boolean =
{
val tile = getTile(world, hit.blockX, hit.blockY, hit.blockZ)
if(tile == null)
return false
val (index, mop) = reduceMOP(hit)
if(tile.partList(index).drawHighlight(mop, player, frame))
return true
tile.partList(index).collisionRayTrace(RayTracer.getStartVec(player), RayTracer.getEndVec(player))
return false
}
}
/**
* Block class for all multiparts, should be internal use only.
*/
class BlockMultipart(id:Int) extends Block(id, Material.rock)
{
import BlockMultipart._
override def hasTileEntity(meta:Int = 0) = true
override def isBlockSolidOnSide(world:World, x:Int, y:Int, z:Int, side:ForgeDirection):Boolean =
getTile(world, x, y, z) match {
case null => false
case tile => tile.isSolid(side.ordinal())
}
override def onNeighborBlockChange(world:World, x:Int, y:Int, z:Int, id:Int)
{
val tile = getTile(world, x, y, z)
if(tile != null)
tile.onNeighborBlockChange()
}
override def collisionRayTrace(world:World, x:Int, y:Int, z:Int, start:Vec3, end:Vec3):ExtendedMOP =
getTile(world, x, y, z) match {
case null => null
case tile => tile.collisionRayTrace(start, end)
}
def rayTraceAll(world:World, x:Int, y:Int, z:Int, start:Vec3, end:Vec3):Iterable[ExtendedMOP] =
getTile(world, x, y, z) match {
case null => Seq()
case tile => tile.rayTraceAll(start, end)
}
override def removeBlockByPlayer(world:World, player:EntityPlayer, x:Int, y:Int, z:Int):Boolean =
{
val hit = RayTracer.retraceBlock(world, player, x, y, z)
val tile = getTile(world, x, y, z)
if(hit == null || tile == null)
{
dropAndDestroy(world, x, y, z)
return true
}
val (index, mop) = reduceMOP(hit)
if(world.isRemote)
{
tile.partList(index).addDestroyEffects(mop, Minecraft.getMinecraft.effectRenderer)
return true
}
tile.harvestPart(index, mop, player)
return world.getBlockTileEntity(x, y, z) == null
}
def dropAndDestroy(world:World, x:Int, y:Int, z:Int)
{
val tile = getTile(world, x, y, z)
if(tile != null && !world.isRemote)
tile.dropItems(getBlockDropped(world, x, y, z, 0, 0))
world.setBlockToAir(x, y, z)
}
override def quantityDropped(meta:Int, fortune:Int, random:Random) = 0
override def getBlockDropped(world:World, x:Int, y:Int, z:Int, meta:Int, fortune:Int):ArrayList[ItemStack] =
{
val ai = new ArrayList[ItemStack]()
if(world.isRemote)
return ai
val tile = getTile(world, x, y, z)
if(tile != null)
tile.partList.foreach(part => part.getDrops.foreach(item => ai.add(item)))
return ai
}
override def addCollisionBoxesToList(world:World, x:Int, y:Int, z:Int, ebb:AxisAlignedBB, list$:List[_], entity:Entity)
{
val list = list$.asInstanceOf[List[AxisAlignedBB]]
val tile = getTile(world, x, y, z)
if(tile != null)
tile.partList.foreach(part =>
part.getCollisionBoxes.foreach{c =>
val aabb = c.toAABB.offset(x, y, z)
if(aabb.intersectsWith(ebb))
list.add(aabb)
})
}
override def addBlockHitEffects(world:World, hit:MovingObjectPosition, effectRenderer:EffectRenderer):Boolean =
{
val tile = getClientTile(world, hit.blockX, hit.blockY, hit.blockZ)
if(tile != null) {
val (index, mop) = reduceMOP(hit)
tile.partList(index).addHitEffects(mop, effectRenderer)
}
return true
}
override def addBlockDestroyEffects(world:World, x:Int, y:Int, z:Int, s:Int, effectRenderer:EffectRenderer) = true
override def renderAsNormalBlock() = false
override def isOpaqueCube = false
override def getRenderType = TileMultipart.renderID
override def isAirBlock(world:World, x:Int, y:Int, z:Int):Boolean =
getTile(world, x, y, z) match {
case null => true
case tile => tile.partList.isEmpty
}
override def isBlockReplaceable(world:World, x:Int, y:Int, z:Int) = isAirBlock(world, x, y, z)
override def getRenderBlockPass = 1
override def canRenderInPass(pass:Int):Boolean =
{
MultipartRenderer.pass = pass
return true
}
override def getPickBlock(hit:MovingObjectPosition, world:World, x:Int, y:Int, z:Int):ItemStack =
{
val tile = getTile(world, x, y, z)
if(tile != null)
{
val (index, mop) = reduceMOP(hit)
return tile.partList(index).pickItem(mop)
}
return null
}
override def getPlayerRelativeBlockHardness(player:EntityPlayer, world:World, x:Int, y:Int, z:Int):Float =
{
val hit = RayTracer.retraceBlock(world, player, x, y, z)
val tile = getTile(world, x, y, z)
if(hit != null && tile != null) {
val (index, mop) = reduceMOP(hit)
return tile.partList(index).getStrength(mop, player)/30F
}
return 1/100F
}
/**
* Kludge to set PROTECTED blockIcon to a blank icon
*/
override def registerIcons(register:IconRegister)
{
val icon = TextureUtils.getBlankIcon(16, register)
setTextureName(icon.getIconName)
super.registerIcons(register)
}
override def getLightValue(world:IBlockAccess, x:Int, y:Int, z:Int):Int =
getTile(world, x, y, z) match {
case null => 0
case tile => tile.getLightValue
}
override def randomDisplayTick(world:World, x:Int, y:Int, z:Int, random:Random)
{
val tile = getClientTile(world, x, y, z)
if(tile != null)
tile.randomDisplayTick(random)
}
override def onBlockActivated(world:World, x:Int, y:Int, z:Int, player:EntityPlayer, side:Int, hitX:Float, hitY:Float, hitZ:Float):Boolean =
{
val hit = RayTracer.retraceBlock(world, player, x, y, z)
if(hit == null)
return false
val tile = getTile(world, x, y, z)
if(tile == null)
return false
val (index, mop) = reduceMOP(hit)
return tile.partList(index).activate(player, mop, player.getHeldItem)
}
override def onBlockClicked(world:World, x:Int, y:Int, z:Int, player:EntityPlayer)
{
val hit = RayTracer.retraceBlock(world, player, x, y, z)
if(hit == null)
return
val tile = getTile(world, x, y, z)
if(tile == null)
return
val (index, mop) = reduceMOP(hit)
tile.partList(index).click(player, mop, player.getHeldItem)
}
override def isProvidingStrongPower(world:IBlockAccess, x:Int, y:Int, z:Int, side:Int):Int =
getTile(world, x, y, z) match {
case null => 0
case tile => tile.strongPowerLevel(side^1)
}
override def isProvidingWeakPower(world:IBlockAccess, x:Int, y:Int, z:Int, side:Int):Int =
getTile(world, x, y, z) match {
case null => 0
case tile => tile.weakPowerLevel(side^1)
}
override def canConnectRedstone(world:IBlockAccess, x:Int, y:Int, z:Int, side:Int):Boolean =
getTile(world, x, y, z) match {
case null => false
case tile => tile.canConnectRedstone(side)
}
override def onEntityCollidedWithBlock(world:World, x:Int, y:Int, z:Int, entity:Entity)
{
val tile = getTile(world, x, y, z)
if(tile != null)
tile.onEntityCollision(entity)
}
override def onNeighborTileChange(world:World, x:Int, y:Int, z:Int, tileX:Int, tileY:Int, tileZ:Int)
{
getTile(world, x, y, z) match {
case null => world.setBlockToAir(x, y, z)
case tile => tile.onNeighborTileChange(tileX, tileY, tileZ)
}
}
override def getExplosionResistance(entity:Entity, world:World, x:Int, y:Int, z:Int, explosionX:Double, explosionY:Double, explosionZ:Double):Float =
getTile(world, x, y, z) match {
case null => 0
case tile => tile.getExplosionResistance(entity)
}
override def weakTileChanges() = true
override def canProvidePower = true
}

View file

@ -0,0 +1,71 @@
package codechicken.multipart
import cpw.mods.fml.client.registry.KeyBindingRegistry.KeyHandler
import net.minecraft.client.settings.KeyBinding
import org.lwjgl.input.Keyboard
import cpw.mods.fml.common.registry.LanguageRegistry
import java.util.EnumSet
import cpw.mods.fml.common.TickType
import net.minecraft.client.Minecraft
import codechicken.lib.packet.PacketCustom
import codechicken.multipart.handler.MultipartCPH
import scala.collection.mutable.HashMap
import net.minecraft.entity.player.EntityPlayer
/**
* A class that maintains a map server<->client of which players are holding the control (or placement modifier key) much like sneaking.
*/
object ControlKeyModifer
{
implicit def playerControlValue(p:EntityPlayer) = new ControlKeyValue(p)
class ControlKeyValue(p:EntityPlayer)
{
def isControlDown = map(p)
}
val map = HashMap[EntityPlayer, Boolean]().withDefaultValue(false)
/**
* Implicit static for Java users.
*/
def isControlDown(p:EntityPlayer) = p.isControlDown
}
/**
* Key Handler implementation
*/
object ControlKeyHandler extends KeyHandler (
Array(new KeyBinding("key.control", Keyboard.KEY_LCONTROL)),
Array(false))
{
import ControlKeyModifer._
LanguageRegistry.instance.addStringLocalization("key.control", "Placement Modifier")
def keyDown(types:EnumSet[TickType], kb:KeyBinding, tickEnd:Boolean, isRepeat:Boolean)
{
if(!tickEnd && Minecraft.getMinecraft.getNetHandler != null)
{
map.put(Minecraft.getMinecraft.thePlayer, true)
val packet = new PacketCustom(MultipartCPH.channel, 1)
packet.writeBoolean(true)
packet.sendToServer()
}
}
def keyUp(types:EnumSet[TickType], kb:KeyBinding, tickEnd:Boolean)
{
if(!tickEnd && Minecraft.getMinecraft.getNetHandler != null)
{
map.put(Minecraft.getMinecraft.thePlayer, false)
val packet = new PacketCustom(MultipartCPH.channel, 1)
packet.writeBoolean(false)
packet.sendToServer()
}
}
def getLabel = "Control Key Modifer"
def ticks = EnumSet.of(TickType.CLIENT)
}

View file

@ -0,0 +1,33 @@
package codechicken.multipart
import codechicken.lib.data.MCDataOutput
import codechicken.lib.data.MCDataInput
/**
* Class for reading and writing ids, widening the carrier data type as necessary
*/
class IDWriter
{
var write:(MCDataOutput, Int)=>Unit = _
var read:(MCDataInput)=>Int = _
def setMax(i:Int)
{
val l = i.toLong & 0xFFFFFFFF
if(l > 0xFFFF)
{
write = (data, i) => data.writeInt(i)
read = (data) => data.readInt()
}
else if(l > 0xFF)
{
write = (data, i) => data.writeShort(i)
read = (data) => data.readUShort()
}
else
{
write = (data, i) => data.writeByte(i)
read = (data) => data.readUByte()
}
}
}

View file

@ -0,0 +1,15 @@
package codechicken.multipart
/**
* Mixin interface for parts that want to be notified of neighbor tile change events (comparators or inventory maintainers)
*/
trait INeighborTileChange {
/**
* Returns whether this part needs calls for tile changes through one solid block
*/
def weakTileChanges():Boolean
/**
* Callback for neighbor tile changes, from same function in Block
*/
def onNeighborTileChanged(side:Int, weak:Boolean)
}

View file

@ -0,0 +1,16 @@
package codechicken.multipart
import java.util.Random
/**
* Interface for parts that need random display ticks (torches)
*
* Marker interface for TRandomDisplayTickTile
*/
trait IRandomDisplayTick
{
/**
* Called on a random display tick.
*/
def randomDisplayTick(random:Random)
}

View file

@ -0,0 +1,12 @@
package codechicken.multipart
/**
* Interface for parts with random update ticks.
*/
trait IRandomUpdateTick
{
/**
* Called on random update. Random ticks are between 800 and 1600 ticks from their last scheduled/random tick
*/
def randomUpdate()
}

View file

@ -0,0 +1,243 @@
package codechicken.multipart
import net.minecraft.world.World
import net.minecraft.block.Block
import net.minecraft.world.IBlockAccess
import net.minecraft.util.Direction
import codechicken.lib.vec.Rotation._
/**
* Interface for parts with redstone interaction
*
* Marker interface for TRedstoneTile. This means that if a part is an instance of IRedstonePart, the container tile may be cast to TRedstoneTile
*/
trait IRedstonePart
{
/**
* Returns the strong (indirect, through blocks) signal being emitted by this part to the specified side
*/
def strongPowerLevel(side:Int):Int
/**
* Returns the weak (direct) signal being emitted by this part to the specified side
*/
def weakPowerLevel(side:Int):Int
/**
* Returns true if this part can connect to redstone on the specified side. Blocking parts like covers will be handled by RedstoneInteractions
*/
def canConnectRedstone(side:Int):Boolean
}
/**
* For parts like wires that adhere to a specific face, reduces redstone connections to the specific edge between two faces.
* Should be implemented on parts implementing TFacePart
*/
trait IFaceRedstonePart extends IRedstonePart
{
/**
* Return the face to which this redstone part is attached
*/
def getFace:Int
}
/**
* For parts that want to define their own connection masks (like center-center parts)
*/
trait IMaskedRedstonePart extends IRedstonePart
{
/**
* Returns the redstone connection mask for this part on side. See IRedstoneConnector for mask definition
*/
def getConnectionMask(side:Int):Int
}
/**
* Interface for tile entities which split their redstone connections into a mask for each side (edges and center)
*
* All connection masks are a 5 bit map.
* The lowest 4 bits correspond to the connection toward the face specified Rotation.rotateSide(side&6, b) where b is the bit index from lowest to highest.
* Bit 5 corresponds to a connection opposite side.
*/
trait IRedstoneConnector
{
/**
* Returns the redstone connection mask for this tile on side.
*/
def getConnectionMask(side:Int):Int
/**
* Returns the weak power level provided by this tile on side through mask
*/
def weakPowerLevel(side:Int, mask:Int):Int
}
/**
* Internal interface for TileMultipart instances hosting IRedstonePart
*/
trait IRedstoneTile extends IRedstoneConnector
{
/**
* Returns a mask of spaces through which a wire could connect on side
*/
def openConnections(side:Int):Int
}
/**
* Block version of IRedstoneConnector
*/
trait IRedstoneConnectorBlock
{
def getConnectionMask(world:IBlockAccess, x:Int, y:Int, z:Int, side:Int):Int
def weakPowerLevel(world:IBlockAccess, x:Int, y:Int, z:Int, side:Int, mask:Int):Int
}
/**
* static helper class for calculating various things about redstone.
* Indirect power (also known as strong power) is not handled here, just use world.getIndirectPowerTo
* Masks are defined in IRedstoneConnector
*/
object RedstoneInteractions
{
import net.minecraft.util.Facing._
val vanillaSideMap = Array(-2, -1, 0, 2, 3, 1)
val sideVanillaMap = Array(1, 2, 5, 3, 4)
/**
* Get the direct power to p on side
*/
def getPowerTo(p:TMultiPart, side:Int):Int =
{
val tile = p.tile
return getPowerTo(tile.worldObj, tile.xCoord, tile.yCoord, tile.zCoord, side,
tile.asInstanceOf[IRedstoneTile].openConnections(side)&connectionMask(p, side))
}
/**
* Get the direct power level to space (x, y, z) on side with mask
*/
def getPowerTo(world:World, x:Int, y:Int, z:Int, side:Int, mask:Int):Int =
getPower(world, x+offsetsXForSide(side), y+offsetsYForSide(side), z+offsetsZForSide(side), side^1, mask)
/**
* Get the direct power level provided by space (x, y, z) on side with mask
*/
def getPower(world:World, x:Int, y:Int, z:Int, side:Int, mask:Int):Int =
{
val tile = world.getBlockTileEntity(x, y, z)
if(tile.isInstanceOf[IRedstoneConnector])
return tile.asInstanceOf[IRedstoneConnector].weakPowerLevel(side, mask)
val block = Block.blocksList(world.getBlockId(x, y, z))
if(block == null)
return 0
if(block.isInstanceOf[IRedstoneConnectorBlock])
return block.asInstanceOf[IRedstoneConnectorBlock].weakPowerLevel(world, x, y, z, side, mask)
val vmask = vanillaConnectionMask(block, world, x, y, z, side, true)
if((vmask&mask) > 0)
{
var m = world.getIndirectPowerLevelTo(x, y, z, side^1)
if(m < 15 && block == Block.redstoneWire)
m = Math.max(m, world.getBlockMetadata(x, y, z))//painful vanilla kludge
return m
}
return 0
}
def vanillaToSide(vside:Int) = sideVanillaMap(vside+1)
/**
* Get the connection mask of the block on side of (x, y, z).
* @param power, whether the connection mask is for signal transfer or visual connection. (some blocks accept power without visual connection)
*/
def otherConnectionMask(world:IBlockAccess, x:Int, y:Int, z:Int, side:Int, power:Boolean):Int =
getConnectionMask(world, x+offsetsXForSide(side), y+offsetsYForSide(side), z+offsetsZForSide(side), side^1, power)
/**
* Get the connection mask of part on side
*/
def connectionMask(p:TMultiPart, side:Int):Int =
{
if(p.isInstanceOf[IRedstonePart] && p.asInstanceOf[IRedstonePart].canConnectRedstone(side))
{
if(p.isInstanceOf[IFaceRedstonePart])
{
val fside = p.asInstanceOf[IFaceRedstonePart].getFace
if((side&6) == (fside&6))
return 0x10
return 1<<rotationTo(side&6, fside)
}
else if(p.isInstanceOf[IMaskedRedstonePart])
{
return p.asInstanceOf[IMaskedRedstonePart].getConnectionMask(side)
}
return 0x1F
}
return 0
}
/**
* @param power If true, don't test canConnectRedstone on blocks, just get a power transmission mask rather than a visual connection
*/
def getConnectionMask(world:IBlockAccess, x:Int, y:Int, z:Int, side:Int, power:Boolean):Int =
{
val tile = world.getBlockTileEntity(x, y, z)
if(tile.isInstanceOf[IRedstoneConnector])
return tile.asInstanceOf[IRedstoneConnector].getConnectionMask(side)
val block = Block.blocksList(world.getBlockId(x, y, z))
if(block == null)
return 0
if(block.isInstanceOf[IRedstoneConnectorBlock])
return block.asInstanceOf[IRedstoneConnectorBlock].getConnectionMask(world, x, y, z, side)
return vanillaConnectionMask(block, world, x, y, z, side, power)
}
/**
* Returns the connection mask for a vanilla block
*/
def vanillaConnectionMask(block:Block, world:IBlockAccess, x:Int, y:Int, z:Int, side:Int, power:Boolean):Int =
{
if(block == Block.torchRedstoneActive || block == Block.torchRedstoneIdle || block == Block.lever || block == Block.stoneButton || block == Block.woodenButton)
return 0x1F
if(side == 0)//vanilla doesn't handle side 0
{
if(power)
return 0x1F
return 0
}
if(block == Block.redstoneWire)
{
if(side != 1)
return 4
return 0x1F
}
if(block == Block.redstoneComparatorActive || block == Block.redstoneComparatorIdle)
{
if(side != 1)
return 4
return 0
}
val vside = vanillaSideMap(side)
if(block == Block.redstoneRepeaterActive || block == Block.redstoneRepeaterIdle)//stupid minecraft hardcodes
{
val meta = world.getBlockMetadata(x, y, z)
if(vside == (meta & 3) || vside == Direction.rotateOpposite(meta & 3))
return 4
return 0
}
if(power || block.canConnectRedstone(world, x, y, z, vside))//some blocks accept power without visualising connections
return 0x1F
return 0
}
}

View file

@ -0,0 +1,54 @@
package codechicken.multipart
import net.minecraft.item.Item
import codechicken.lib.vec.Vector3
import codechicken.lib.vec.Rotation
import net.minecraft.item.ItemStack
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.world.World
import codechicken.lib.vec.BlockCoord
/**
* Java class implementation
*/
abstract class JItemMultiPart(id:Int) extends Item(id) with TItemMultiPart
/**
* Simple multipart item class for easy placement. Simply override the newPart function and it the part will be added to the block space if it passes the occlusion tests.
*/
trait TItemMultiPart extends Item
{
def getHitDepth(vhit:Vector3, side:Int):Double =
vhit.copy.scalarProject(Rotation.axes(side)) + (side%2^1)
override def onItemUse(item:ItemStack, player:EntityPlayer, world:World, x:Int, y:Int, z:Int, side:Int, hitX:Float, hitY:Float, hitZ:Float):Boolean =
{
val pos = new BlockCoord(x, y, z)
val vhit = new Vector3(hitX, hitY, hitZ)
val d = getHitDepth(vhit, side)
def place():Boolean =
{
val part = newPart(item, player, world, pos, side, vhit)
if(part == null || !TileMultipart.canPlacePart(world, pos, part))
return false
if(!world.isRemote)
TileMultipart.addPart(world, pos, part)
if(!player.capabilities.isCreativeMode)
item.stackSize-=1
return true
}
if(d < 1 && place())
return true
pos.offset(side)
return place()
}
/**
* Create a new part based on the placement information parameters.
*/
def newPart(item:ItemStack, player:EntityPlayer, world:World, pos:BlockCoord, side:Int, vhit:Vector3):TMultiPart
}

View file

@ -0,0 +1,188 @@
package codechicken.multipart
import scala.collection.mutable.HashMap
import codechicken.lib.packet.PacketCustom
import codechicken.lib.data.MCDataOutput
import codechicken.lib.data.MCDataInput
import net.minecraft.world.World
import codechicken.lib.vec.BlockCoord
import scala.collection.mutable.ListBuffer
import cpw.mods.fml.common.ModContainer
import cpw.mods.fml.common.Loader
/**
* This class handles the registration and internal ID mapping of all multipart classes.
*/
object MultiPartRegistry
{
/**
* Interface to be registered for constructing parts.
* Every instance of every multipart is constructed from an implementor of this.
*/
trait IPartFactory
{
/**
* Create a new instance of the part with the specified type name identifier
* @param client If the part instance is for the client or the server
*/
def createPart(name:String, client:Boolean):TMultiPart
}
/**
* An interface for converting existing blocks/tile entities to multipart versions.
*/
trait IPartConverter
{
/**
* Return true if this converter can handle the specific blockID (may or may not actually convert the block)
*/
def canConvert(blockID:Int):Boolean
/**
* Return a multipart version of the block at pos in world. Return null if no conversion is possible.
*/
def convert(world:World, pos:BlockCoord):TMultiPart
}
private val typeMap:HashMap[String, (Boolean)=>TMultiPart] = new HashMap
private val nameMap:HashMap[String, Int] = new HashMap
private var idMap:Array[(String, (Boolean)=>TMultiPart)] = _
private val idWriter = new IDWriter
private val converters:Array[Seq[IPartConverter]] = Array.fill(4096)(Seq())
private val containers:HashMap[String, ModContainer] = new HashMap()
/**
* The state of the registry. 0 = no parts, 1 = registering, 2 = registered
*/
private var state:Int = 0
/**
* Register a part factory with an array of types it is capable of instantiating. Must be called before postInit
*/
def registerParts(partFactory:IPartFactory, types:Array[String])
{
registerParts(partFactory.createPart _, types:_*)
}
/**
* Scala function version of registerParts
*/
def registerParts(partFactory:(String, Boolean)=>TMultiPart, types:String*)
{
if(loaded)
throw new IllegalStateException("Parts must be registered in the init methods.")
state=1
val container = Loader.instance.activeModContainer
if(container == null)
throw new IllegalStateException("Parts must be registered during the initialization phase of a mod container")
types.foreach{s =>
if(typeMap.contains(s))
throw new IllegalStateException("Part with id "+s+" is already registered.")
typeMap.put(s, (c:Boolean) => partFactory(s, c))
containers.put(s, container)
}
}
/**
* Register a part converter instance
*/
def registerConverter(c:IPartConverter)
{
for(i <- 0 until 4096)
if(c.canConvert(i))
converters(i) = converters(i):+c
}
private[multipart] def beforeServerStart()
{
idMap = typeMap.toList.sortBy(_._1).toArray
idWriter.setMax(idMap.length)
nameMap.clear()
for(i <- 0 until idMap.length)
nameMap.put(idMap(i)._1, i)
}
private[multipart] def writeIDMap(packet:PacketCustom)
{
packet.writeInt(idMap.length)
idMap.foreach(e => packet.writeString(e._1))
}
private[multipart] def readIDMap(packet:PacketCustom):Seq[String] =
{
val k = packet.readInt()
idWriter.setMax(k)
idMap = new Array(k)
nameMap.clear()
val missing = ListBuffer[String]()
for(i <- 0 until k)
{
val s = packet.readString()
val v = typeMap.get(s)
if(v.isEmpty)
missing+=s
else {
idMap(i) = (s, v.get)
nameMap.put(s, i)
}
}
return missing
}
/**
* Return true if any multiparts have been registered
*/
private[multipart] def required = state > 0
/**
* Return true if no more parts can be registered
*/
def loaded = state == 2
private[multipart] def postInit(){state = 2}
/**
* Writes the id of part to data
*/
def writePartID(data:MCDataOutput, part:TMultiPart)
{
idWriter.write(data, nameMap.get(part.getType).get)
}
/**
* Uses instantiators to creat a new part from the id read from data
*/
def readPart(data:MCDataInput) = idMap(idWriter.read(data))._2(true)
/**
* Uses instantiators to create a new part with specified identifier on side
*/
def createPart(name:String, client:Boolean):TMultiPart =
{
val part = typeMap.get(name)
if(part.isDefined)
return part.get(client)
else
{
System.err.println("Missing mapping for part with ID: "+name)
return null
}
}
/**
* Calls converters to create a multipart version of the block at pos
*/
def convertBlock(world:World, pos:BlockCoord, id:Int):TMultiPart =
{
converters(id).foreach{c =>
val ret = c.convert(world, pos)
if(ret != null)
return ret
}
return null
}
def getModContainer(name:String) = containers(name)
}

View file

@ -0,0 +1,219 @@
package codechicken.multipart
import net.minecraft.tileentity.TileEntity
import scala.collection.immutable.Map
import net.minecraft.world.World
import codechicken.lib.vec.BlockCoord
import codechicken.multipart.handler.MultipartProxy
import codechicken.lib.packet.PacketCustom
import net.minecraft.network.packet.Packet53BlockChange
import codechicken.multipart.asm.IMultipartFactory
import codechicken.multipart.asm.ASMMixinFactory
/**
* This class manages the dynamic construction and allocation of container TileMultipart instances.
*
* Classes that extend TileMultipart, adding tile centric logic, optimisations or interfaces, can be registered to a marker interface on a part instance.
* When a part is added to the tile that implements the certain marker interface, the container tile will be replaced with a class that includes the functionality from the corresponding mixin class.
*
* Classes are generated in a similar fashion to the way scala traits are compiled. To see the output, simply enable the config option and look in the asm/multipart folder of you .minecraft directory.
*
* There are several mixin traits that come with the API included in the scalatraits package. TPartialOcclusionTile is defined as class instead of trait to give an example for Java programmers.
*/
object MultipartGenerator
{
private var tileTraitMap:Map[Class[_], Set[String]] = Map()
private var interfaceTraitMap_c:Map[String, String] = Map()
private var interfaceTraitMap_s:Map[String, String] = Map()
private var partTraitMap_c:Map[Class[_], Seq[String]] = Map()
private var partTraitMap_s:Map[Class[_], Seq[String]] = Map()
var factory:IMultipartFactory = ASMMixinFactory
def partTraitMap(client:Boolean) = if(client) partTraitMap_c else partTraitMap_s
def interfaceTraitMap(client:Boolean) = if(client) partTraitMap_c else interfaceTraitMap_s
def traitsForPart(part:TMultiPart, client:Boolean):Seq[String] =
{
var ret = partTraitMap(client).getOrElse(part.getClass, null)
if(ret == null)
{
def heirachy(clazz:Class[_]):Seq[Class[_]] =
{
var superClasses:Seq[Class[_]] = clazz.getInterfaces.flatMap(c => heirachy(c)):+clazz
if(clazz.getSuperclass != null)
superClasses = superClasses++heirachy(clazz.getSuperclass)
return superClasses
}
val interfaceTraitMap = if(client) interfaceTraitMap_c else interfaceTraitMap_s
ret = heirachy(part.getClass).flatMap(c => interfaceTraitMap.get(c.getName)).distinct
if(client)
partTraitMap_c = partTraitMap_c+(part.getClass -> ret)
else
partTraitMap_s = partTraitMap_s+(part.getClass -> ret)
}
return ret
}
/**
* Check if part adds any new interfaces to tile, if so, replace tile with a new copy and call tile.addPart(part)
* returns true if tile was replaced
*/
private[multipart] def addPart(world:World, pos:BlockCoord, part:TMultiPart):TileMultipart =
{
val (tile, converted) = TileMultipart.getOrConvertTile2(world, pos)
var partTraits = traitsForPart(part, world.isRemote)
var ntile = tile
if(ntile != null)
{
if(converted)//perform client conversion
{
ntile.partList(0).invalidateConvertedTile()
world.setBlock(pos.x, pos.y, pos.z, MultipartProxy.block.blockID, 0, 0)
silentAddTile(world, pos, ntile)
PacketCustom.sendToChunk(new Packet53BlockChange(pos.x, pos.y, pos.z, world), world, pos.x>>4, pos.z>>4)
ntile.partList(0).onConverted()
ntile.writeAddPart(ntile.partList(0))
}
val tileTraits = tileTraitMap(tile.getClass)
partTraits = partTraits.filter(!tileTraits(_))
if(!partTraits.isEmpty)
{
ntile = factory.generateTile(partTraits++tileTraits, world.isRemote)
tile.setValid(false)
silentAddTile(world, pos, ntile)
ntile.from(tile)
}
}
else
{
world.setBlock(pos.x, pos.y, pos.z, MultipartProxy.block.blockID, 0, 0)
ntile = factory.generateTile(partTraits, world.isRemote)
silentAddTile(world, pos, ntile)
}
ntile.addPart_impl(part)
return ntile
}
/**
* Adds a tile entity to the world without notifying neighbor blocks or adding it to the tick list
*/
def silentAddTile(world:World, pos:BlockCoord, tile:TileEntity)
{
val chunk = world.getChunkFromBlockCoords(pos.x, pos.z)
if(chunk != null)
chunk.setChunkBlockTileEntity(pos.x & 15, pos.y, pos.z & 15, tile)
}
/**
* Check if tile satisfies all the interfaces required by parts. If not, return a new generated copy of tile
*/
private[multipart] def generateCompositeTile(tile:TileEntity, parts:Seq[TMultiPart], client:Boolean):TileMultipart =
{
var partTraits = parts.flatMap(traitsForPart(_, client)).distinct
if(tile != null && tile.isInstanceOf[TileMultipart])
{
var tileTraits = tileTraitMap(tile.getClass)
if(partTraits.forall(tileTraits(_)) && partTraits.size == tileTraits.size)//equal contents
return tile.asInstanceOf[TileMultipart]
}
return factory.generateTile(partTraits, client)
}
/**
* Check if there are any redundant interfaces on tile, if so, replace tile with new copy
*/
private[multipart] def partRemoved(tile:TileMultipart, part:TMultiPart):TileMultipart =
{
val client = tile.worldObj.isRemote
var partTraits = tile.partList.flatMap(traitsForPart(_, client))
var testSet = partTraits.toSet
if(!traitsForPart(part, client).forall(testSet(_)))
{
val ntile = factory.generateTile(testSet.toSeq, client)
tile.setValid(false)
silentAddTile(tile.worldObj, new BlockCoord(tile), ntile)
ntile.from(tile)
ntile.notifyTileChange()
return ntile
}
return tile
}
/**
* register s_trait to be applied to tiles containing parts implementing s_interface
*/
def registerTrait(s_interface:String, s_trait:String):Unit = registerTrait(s_interface, s_trait, s_trait)
/**
* register traits to be applied to tiles containing parts implementing s_interface
* s_trait for server worlds (may be null)
* c_trait for client worlds (may be null)
*/
def registerTrait(s_interface:String, c_trait:String, s_trait:String)
{
if(c_trait != null)
{
if(interfaceTraitMap_c.contains(s_interface))
System.err.println("Trait already registered for "+s_interface)
else
{
interfaceTraitMap_c = interfaceTraitMap_c+(s_interface->c_trait)
factory.registerTrait(s_interface, c_trait, true)
}
}
if(s_trait != null)
{
if(interfaceTraitMap_s.contains(s_interface))
System.err.println("Trait already registered for "+s_interface)
else
{
interfaceTraitMap_s = interfaceTraitMap_s+(s_interface->s_trait)
factory.registerTrait(s_interface, s_trait, false)
}
}
}
def registerPassThroughInterface(s_interface:String):Unit = registerPassThroughInterface(s_interface, true, true)
/**
* A passthrough interface, is an interface to be implemented on the container tile instance, for which all calls are passed directly to the single implementing part.
* Registering a passthrough interface is equivalent to defining a mixin class as follows.
* 1. field 'impl' which contains the reference to the corresponding part
* 2. occlusionTest is overriden to prevent more than one part with s_interface existing in the block space
* 3. implementing s_interface and passing all calls directly to the part instance.
*
* This allows compatibility with APIs that expect interfaces on the tile entity.
*/
def registerPassThroughInterface(s_interface:String, client:Boolean, server:Boolean)
{
val tType = factory.generatePassThroughTrait(s_interface)
if(tType == null)
return
if(client)
{
if(interfaceTraitMap_c.contains(s_interface))
System.err.println("Trait already registered for "+s_interface)
else
interfaceTraitMap_c = interfaceTraitMap_c+(s_interface->tType)
}
if(server)
{
if(interfaceTraitMap_s.contains(s_interface))
System.err.println("Trait already registered for "+s_interface)
else
interfaceTraitMap_s = interfaceTraitMap_s+(s_interface->tType)
}
}
private[multipart] def registerTileClass(clazz:Class[_ <: TileEntity], traits:Set[String])
{
tileTraitMap=tileTraitMap+(clazz->traits)
MultipartProxy.onTileClassBuilt(clazz)
}
}

View file

@ -0,0 +1,69 @@
package codechicken.multipart
import net.minecraft.tileentity.TileEntity
import net.minecraft.nbt.NBTTagCompound
import net.minecraft.world.World
import codechicken.multipart.handler.MultipartSaveLoad
import com.google.common.collect.LinkedListMultimap
import scala.collection.JavaConversions._
import codechicken.multipart.handler.MultipartSPH
import net.minecraft.world.WorldServer
import java.util.Arrays
import net.minecraft.server.management.PlayerInstance
import codechicken.lib.asm.ObfMapping
/**
* Static helper class for handling the unusual way that multipart tile entities load from nbt and send description packets
* <br>
* Multipart tile entities will all save themselves with the id "savedMultipart" which if normally loaded by minecraft,
* will create a dummy tile entity which just holds the NBT it was read from. These dummies are then converted to actual container tiles on the ChunkLoad event.
* The createTileFromNBT function should be used to construct a multipart tile from NBT without the ChunkLoad event.
* <br>
* Multipart tile entities do not send description packets via the conventional means of one packet per tile when PlayerInstance calls for it, to do so would be terribly inefficient.
* Instead, the ChunkWatch event is used to batch all the describing data for a chunk into one packet which is compressed using relative positions.
* The sendDescPacket(s) functions should be used to send the description packet of a tile or tiles without a ChunkWatch event.
* <br>
* An example of using this class to move blocks/tile entites around can be found at www.chickenbones.craftsaddle.org/Files/Other/ItemDevTool2.java
*/
object MultipartHelper
{
val f_playersInChunk = classOf[PlayerInstance].getDeclaredField(
new ObfMapping("net/minecraft/server/management/PlayerInstance", "playersInChunk", "Ljava/util/List;")
.toRuntime.s_name)
f_playersInChunk.setAccessible(true)
def playersInChunk(inst:PlayerInstance) = f_playersInChunk.get(inst).asInstanceOf[List[_]]
def createTileFromNBT(world:World, tag:NBTTagCompound):TileEntity = {
if(!tag.getString("id").equals("savedMultipart"))
return null
MultipartSaveLoad.loadingWorld = world
return TileMultipart.createFromNBT(tag)
}
/**
* Note. This method should only be used to send tiles that have been created on the server mid-game via an NBT load to clients.
*/
def sendDescPacket(world:World, tile:TileEntity) {
val c = world.getChunkFromBlockCoords(tile.xCoord, tile.zCoord)
val pkt = MultipartSPH.getDescPacket(c, Arrays.asList(tile).iterator)
if(pkt != null)
pkt.sendToChunk(world, c.xPosition, c.zPosition)
}
def sendDescPackets(world:World, tiles:Iterable[TileEntity]) {
val map = LinkedListMultimap.create[Long, TileEntity]()
tiles.filter(_.isInstanceOf[TileMultipart]).foreach(t => map.put(t.xCoord.toLong<<32|t.zCoord, t))
val mgr = world.asInstanceOf[WorldServer].getPlayerManager
map.asMap.entrySet.foreach{e =>
val coord = e.getKey
val c = world.getChunkFromBlockCoords((coord>>32).toInt, coord.toInt)
lazy val pkt = MultipartSPH.getDescPacket(c, e.getValue.iterator)
val inst = mgr.getOrCreateChunkWatcher(c.xPosition, c.zPosition, false)
if(!playersInChunk(inst).isEmpty)
inst.sendToAllPlayersWatchingChunk(pkt.toPacket)
}
}
}

View file

@ -0,0 +1,78 @@
package codechicken.multipart
import codechicken.lib.render.CCRenderState
import codechicken.lib.vec.Vector3
import cpw.mods.fml.client.registry.ISimpleBlockRenderingHandler
import cpw.mods.fml.client.registry.RenderingRegistry
import net.minecraft.block.Block
import net.minecraft.client.renderer.RenderBlocks
import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer
import net.minecraft.tileentity.TileEntity
import net.minecraft.world.IBlockAccess
import net.minecraft.client.Minecraft
import cpw.mods.fml.relauncher.SideOnly
import cpw.mods.fml.relauncher.Side
import codechicken.lib.raytracer.ExtendedMOP
import codechicken.lib.lighting.LazyLightMatrix
/**
* Internal class for rendering callbacks. Should be moved to the handler package
*/
@SideOnly(Side.CLIENT)
object MultipartRenderer extends TileEntitySpecialRenderer with ISimpleBlockRenderingHandler
{
TileMultipart.renderID = RenderingRegistry.getNextAvailableRenderId
private val olm = new LazyLightMatrix
var pass:Int = 0
override def renderTileEntityAt(t:TileEntity, x:Double, y:Double, z:Double, f:Float)
{
val tmpart = t.asInstanceOf[TileMultipartClient]
if(tmpart.partList.isEmpty)
return
CCRenderState.reset()
CCRenderState.pullLightmap()
CCRenderState.useNormals(true)
val pos = new Vector3(x, y, z)
tmpart.renderDynamic(pos, f, pass)
}
override def getRenderId = TileMultipart.renderID
override def renderWorldBlock(world:IBlockAccess, x:Int, y:Int, z:Int, block:Block, modelId:Int, renderer:RenderBlocks):Boolean =
{
val t = world.getBlockTileEntity(x, y, z)
if(!t.isInstanceOf[TileMultipartClient])
return false
val tmpart = t.asInstanceOf[TileMultipartClient]
if(tmpart.partList.isEmpty)
return false
if(renderer.hasOverrideBlockTexture)
{
val hit = Minecraft.getMinecraft.objectMouseOver
if(hit != null && hit.blockX == x && hit.blockY == y && hit.blockZ == z && ExtendedMOP.getData(hit).isInstanceOf[(_, _)])
{
val hitInfo:(Int, _) = ExtendedMOP.getData(hit)
if(hitInfo._1.isInstanceOf[Int] && hitInfo._1 >= 0 && hitInfo._1 < tmpart.partList.size)
tmpart.partList(hitInfo._1).drawBreaking(renderer)
}
return false
}
CCRenderState.reset()
CCRenderState.useModelColours(true)
val pos = new Vector3(x, y, z)
olm.setPos(world, x, y, z)
tmpart.renderStatic(pos, olm, pass)
return true
}
override def renderInventoryBlock(block:Block, meta:Int, modelId:Int, renderer:RenderBlocks) {}
override def shouldRender3DInInventory = false
}

View file

@ -0,0 +1,82 @@
package codechicken.multipart
import scala.collection.mutable.{Map => MMap}
import codechicken.lib.data.MCDataOutput
import codechicken.lib.data.MCDataInput
/**
* Static class for packing update data.
* When a specific property of a part changes and needs sending to the client, a bit can be set in the mask.
* This bit can then be checked in the writeScheduled callback.
* This prevents sending multiple packets if the same property updates more than once per tick.
*/
object PacketScheduler
{
private val map = MMap[TMultiPart, Long]()
/**
* Add bits to the current update mask for part. (binary OR)
*/
def schedulePacket(part:TMultiPart, mask:Long) = {//TODO remove = in 1.7
if(part.world.isRemote)
throw new IllegalArgumentException("Cannot use PacketScheduler on a client world")
map.put(part, map.getOrElse(part, 0L)|mask)
}
private[multipart] def sendScheduled() {
map.foreach{ e =>
val (part, mask) = e
if(part.tile != null) {
val ipart = part.asInstanceOf[IScheduledPacketPart]
val w = part.getWriteStream
ipart.maskWidth match {
case 1 => w.writeByte(mask.toInt)
case 2 => w.writeShort(mask.toInt)
case 4 => w.writeInt(mask.toInt)
case 8 => w.writeLong(mask)
}
ipart.writeScheduled(mask, w)
}
}
map.clear()
}
}
/**
* Callback interface for PacketScheduler
*/
trait IScheduledPacketPart
{
/**
* Write scheduled data to the packet, mask is the cumulative mask from calls to schedulePacket
*/
def writeScheduled(mask:Long, packet:MCDataOutput)
/**
* Returns the width (in bytes) of the data type required to hold all valid mask bits. Valid values are 1, 2, 4 and 8
*/
def maskWidth:Int
/**
* Read data matching mask. Estiablishes a method for subclasses to override. This should be called from read
*/
def readScheduled(mask:Long, packet:MCDataInput)
}
trait TScheduledPacketPart extends TMultiPart with IScheduledPacketPart
{
final override def read(packet:MCDataInput) {
val mask = maskWidth match {
case 1 => packet.readUByte
case 2 => packet.readUShort
case 4 => packet.readInt
case 8 => packet.readLong
}
readScheduled(mask, packet)
}
def writeScheduled(mask:Long, packet:MCDataOutput){}
def readScheduled(mask:Long, packet:MCDataInput){}
}

View file

@ -0,0 +1,134 @@
package codechicken.multipart;
/**
* Defines what each slot in a multipart tile corresponds to and provides some utility functions.
* For performance reasons, it is recommended that integer constants be useds as opposed to this enum
*/
public enum PartMap
{
BOTTOM(0),
TOP(1),
NORTH(2),
SOUTH(3),
WEST(4),
EAST(5),
CENTER(6),
CORNER_NNN(7),//0
CORNER_NPN(8),//1
CORNER_NNP(9),//2
CORNER_NPP(10),//3
CORNER_PNN(11),//4
CORNER_PPN(12),//5
CORNER_PNP(13),//6
CORNER_PPP(14),//7
EDGE_NYN(15),//0
EDGE_NYP(16),//1
EDGE_PYN(17),//2
EDGE_PYP(18),//3
EDGE_NNZ(19),//4
EDGE_PNZ(20),//5
EDGE_NPZ(21),//6
EDGE_PPZ(22),//7
EDGE_XNN(23),//8
EDGE_XPN(24),//9
EDGE_XNP(25),//10
EDGE_XPP(26);//11
public final int i;
public final int mask;
private PartMap(int i)
{
this.i = i;
mask = 1<<i;
}
/**
* Don't actually use this.
*/
public static PartMap face(int i)
{
return values()[i];
}
/**
* Don't actually use this.
*/
public static PartMap edge(int i)
{
return values()[i+15];
}
/**
* Don't actually use this.
*/
public static PartMap corner(int i)
{
return values()[i+7];
}
/**
* Returns a 3 bit mask of the axis xzy that are variable in this edge.
* For example, the first 4 edges (15-18) are along the Y axis, variable in x and z, so the mask is 110b. Note axis y is 1<<0, z is 1<<1 and x is 1<<2
* Note the parameter e is relative to the first edge slot and can range from 0-11
*/
public static int edgeAxisMask(int e)
{
switch(e>>2)
{
case 0: return 6;
case 1: return 5;
case 2: return 3;
}
throw new IllegalArgumentException("Switch Falloff");
}
/**
* Unpacks an edge index, to a mask where high values indicate positive positions in that axis.
* Note the parameter e is relative to the first edge slot and can range from 0-11
* For example, edge 1 (slot 16), is EDGE_NYP so the mask returned would be 010 as z is positive.
*/
public static int unpackEdgeBits(int e)
{
switch(e>>2)
{
case 0: return (e&3)<<1;
case 1: return (e&2)>>1|(e&1)<<2;
case 2: return (e&3);
}
throw new IllegalArgumentException("Switch Falloff");
}
/**
* Repacks a mask of axis bits indicating positive positions, into an edge in along the same axis as e.
* Note the parameter e is relative to the first edge slot and can range from 0-11
*/
public static int packEdgeBits(int e, int bits)
{
switch(e>>2)
{
case 0: return e&0xC|bits>>1;
case 1: return e&0xC|(bits&4)>>2|(bits&1)<<1;
case 2: return e&0xC|bits&3;
}
throw new IllegalArgumentException("Switch Falloff");
}
private static int[] edgeBetweenMap = new int[]{
-1, -1, 8, 10, 4, 5,
-1, -1, 9, 11, 6, 7,
-1, -1,-1, -1, 0, 2,
-1, -1,-1, -1, 1, 3};
/**
* Returns the slot of the edge between 2 sides.
*/
public static int edgeBetween(int s1, int s2)
{
if(s2 < s1)
return edgeBetween(s2, s1);
if((s1&6) == (s2&6))
throw new IllegalArgumentException("Faces "+s1+" and "+s2+" are opposites");
return 15+edgeBetweenMap[s1*6+s2];
}
}

View file

@ -0,0 +1,37 @@
package codechicken.multipart
import codechicken.lib.vec.Cuboid6
import codechicken.lib.raytracer.IndexedCuboid6
import net.minecraft.client.renderer.RenderBlocks
import codechicken.lib.render.CCRenderState
import codechicken.lib.render.RenderUtils
import codechicken.lib.render.IconTransformation
import scala.collection.JavaConversions._
import java.lang.Iterable
import codechicken.lib.vec.Translation
/**
* Java class implementation
*/
abstract class JCuboidPart extends TCuboidPart
/**
* Trait for parts that are simply a cuboid, having one bounding box. Overrides multipart functions to this effect.
*/
trait TCuboidPart extends TMultiPart
{
/**
* Return the bounding Cuboid6 for this part.
*/
def getBounds:Cuboid6
override def getSubParts:Iterable[IndexedCuboid6] = Seq(new IndexedCuboid6(0, getBounds))
override def getCollisionBoxes:Iterable[Cuboid6] = Seq(getBounds)
override def drawBreaking(renderBlocks:RenderBlocks)
{
CCRenderState.reset()
RenderUtils.renderBlock(getBounds, 0, new Translation(x, y, z), new IconTransformation(renderBlocks.overrideBlockTexture), null)
}
}

View file

@ -0,0 +1,12 @@
package codechicken.multipart
/**
* Interface which must be implemented by parts that go in an edge slot.
*/
trait TEdgePart extends TSlottedPart
{
/**
* Return true if this part can conduct redstone signal or let redstone signal pass through it.
*/
def conductsRedstone = false
}

View file

@ -0,0 +1,16 @@
package codechicken.multipart
/**
* Inteface which must be implemented by parts that go in a face part.
*/
trait TFacePart extends TSlottedPart
{
/**
* Passed down from Block.isBlockSolidOnSide. Return true if this part is solid and opaque on the specified side
*/
def solid(side:Int):Boolean = true
/**
* Return the redstone conduction map for which signal can pass through this part on the face. Eg, hollow covers return 0xF as signal can pass through the center hole.
*/
def redstoneConductionMap = 0
}

View file

@ -0,0 +1,81 @@
package codechicken.multipart
import codechicken.lib.vec.Cuboid6
import cpw.mods.fml.relauncher.SideOnly
import cpw.mods.fml.relauncher.Side
import net.minecraft.util.Icon
import net.minecraft.util.MovingObjectPosition
import net.minecraft.client.particle.EffectRenderer
import codechicken.lib.vec.Vector3
import codechicken.lib.raytracer.ExtendedMOP
import codechicken.lib.render.EntityDigIconFX
/**
* This suite of 3 classes provides simple functions for standard minecraft style hit and break particles.
*
* Scala|Java composition setup.
* Due to the lack of mixin inheritance in Java, the classes are structured to suit both languages as follows.
* IconHitEffects contains static implementations of the functions that would be overriden in TMultiPart
* JIconHitEffects is the interface that should be implemented by a Java class,
* which can then override the functions in TMultipart and call the static methods in IconHitEffects with 'this' as the first parameter
* TIconHitEffects is a trait for scala implementors that does includes the overrides/static calls that Java programmers need to include themselves.
*/
object IconHitEffects
{
def addHitEffects(part:JIconHitEffects, hit:MovingObjectPosition, effectRenderer:EffectRenderer)
{
EntityDigIconFX.addBlockHitEffects(part.tile.worldObj,
part.getBounds.copy.add(Vector3.fromTileEntity(part.tile)), hit.sideHit,
part.getBreakingIcon(ExtendedMOP.getData(hit), hit.sideHit), effectRenderer)
}
def addDestroyEffects(part:JIconHitEffects, effectRenderer:EffectRenderer)
{
addDestroyEffects(part, effectRenderer, true)
}
def addDestroyEffects(part:JIconHitEffects, effectRenderer:EffectRenderer, scaleDensity:Boolean)
{
val icons = new Array[Icon](6)
for(i <- 0 until 6)
icons(i) = part.getBrokenIcon(i)
val bounds =
if(scaleDensity) part.getBounds.copy
else Cuboid6.full.copy
EntityDigIconFX.addBlockDestroyEffects(part.tile.worldObj,
bounds.add(Vector3.fromTileEntity(part.tile)), icons, effectRenderer)
}
}
/**
* Java interface containing callbacks for particle rendering.
* Make sure to override addHitEffects and addDestroyEffects as in TIconHitEffects
*/
trait JIconHitEffects extends TMultiPart
{
def getBounds:Cuboid6
@SideOnly(Side.CLIENT)
def getBreakingIcon(subPart:Any, side:Int):Icon = getBrokenIcon(side)
@SideOnly(Side.CLIENT)
def getBrokenIcon(side:Int):Icon
}
/**
* Trait for scala programmers
*/
trait TIconHitEffects extends JIconHitEffects
{
@SideOnly(Side.CLIENT)
override def addHitEffects(hit:MovingObjectPosition, effectRenderer:EffectRenderer)
{
IconHitEffects.addHitEffects(this, hit, effectRenderer)
}
@SideOnly(Side.CLIENT)
override def addDestroyEffects(effectRenderer:EffectRenderer)
{
IconHitEffects.addDestroyEffects(this, effectRenderer)
}
}

View file

@ -0,0 +1,295 @@
package codechicken.multipart
import codechicken.lib.vec.Cuboid6
import codechicken.lib.vec.Vector3
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.item.ItemStack
import net.minecraft.nbt.NBTTagCompound
import net.minecraft.util.MovingObjectPosition
import codechicken.lib.raytracer.IndexedCuboid6
import cpw.mods.fml.relauncher.SideOnly
import cpw.mods.fml.relauncher.Side
import net.minecraft.client.particle.EffectRenderer
import net.minecraft.client.renderer.RenderBlocks
import codechicken.lib.vec.BlockCoord
import net.minecraft.tileentity.TileEntity
import codechicken.lib.lighting.LazyLightMatrix
import codechicken.lib.data.MCDataOutput
import codechicken.lib.data.MCDataInput
import net.minecraft.entity.Entity
import java.lang.Iterable
import scala.collection.JavaConversions._
import net.minecraft.util.Vec3
import codechicken.lib.raytracer.ExtendedMOP
import codechicken.lib.raytracer.RayTracer
abstract class TMultiPart
{
/**
* Reference to the container TileMultipart instance
*/
var tile:TileMultipart = _
/**
* Legacy helper function for getting the tile entity (from when TileMultipart was a trait). Use tile() now.
*/
@Deprecated
def getTile:TileEntity = tile
/**
* Getter for tile.worldObj
*/
def world = if(tile == null) null else tile.worldObj
/**
* Short getter for xCoord
*/
def x = tile.xCoord
/**
* Short getter for yCoord
*/
def y = tile.yCoord
/**
* Short getter for zCoord
*/
def z = tile.zCoord
/**
* The unique string identifier for this class of multipart.
*/
def getType:String
/**
* Called when the container tile instance is changed to update reference
*/
def bind(t:TileMultipart)
{
tile = t
}
/**
* Perform an occlusion test to determine whether this and npart can 'fit' in this block space.
*/
def occlusionTest(npart:TMultiPart):Boolean = true
/**
* Return a list of entity collision boxes.
* Note all Cuboid6's returned by methods in TMultiPart should be within (0,0,0)->(1,1,1)
*/
def getCollisionBoxes:Iterable[Cuboid6] = Seq()
/**
* Perform a raytrace of this part. The default implementation does a Cuboid6 ray trace on bounding boxes returned from getSubParts.
* This should only be overridden if you need special ray-tracing capabilities such as triangular faces.
* The returned ExtendedMOP will be passed to methods such as 'activate' so it is recommended to use the data field to indicate information about the hit area.
*/
def collisionRayTrace(start: Vec3, end: Vec3): ExtendedMOP = {
val offset = new Vector3(x, y, z)
val boxes = getSubParts.map(c => new IndexedCuboid6(c.data, c.copy.add(offset)))
return RayTracer.instance.rayTraceCuboids(new Vector3(start), new Vector3(end), boxes.toList,
new BlockCoord(x, y, z), tile.getBlockType).asInstanceOf[ExtendedMOP]
}
/**
* For the default collisionRayTrace implementation, returns a list of indexed bounding boxes. The data field of ExtendedMOP will be set to the index of the cuboid the raytrace hit.
*/
def getSubParts:Iterable[IndexedCuboid6] = Seq()
/**
* Return a list of items that should be dropped when this part is destroyed.
*/
def getDrops:Iterable[ItemStack] = Seq()
/**
* Return a value indicating how hard this part is to break
* @param hit An instance of ExtendedMOP from collisionRayTrace
*/
def getStrength(hit:MovingObjectPosition, player:EntityPlayer):Float = 1
/**
* Harvest this part, removing it from the container tile and dropping items if necessary.
* @param hit An instance of ExtendedMOP from collisionRayTrace
* @param player The player harvesting the part
*/
def harvest(hit:MovingObjectPosition, player:EntityPlayer)
{
if(!player.capabilities.isCreativeMode)
tile.dropItems(getDrops)
tile.remPart(this)
}
/**
* The light level emitted by this part
*/
def getLightValue = 0
/**
* Explosion resistance of the host tile is the maximum explosion resistance of the contained parts
* @param entity The entity responsible for this explosion
* @return The resistance of this part the the explosion
*/
def explosionResistance(entity:Entity) = 0F
/**
* Add particles and other effects when a player is mining this part
* @param hit An instance of ExtendedMOP from collisionRayTrace
*/
@SideOnly(Side.CLIENT)
def addHitEffects(hit:MovingObjectPosition, effectRenderer:EffectRenderer){}
/**
* Add particles and other effects when a player broke this part
* @param hit An instance of ExtendedMOP from collisionRayTrace
*/
@SideOnly(Side.CLIENT)
def addDestroyEffects(hit:MovingObjectPosition, effectRenderer:EffectRenderer) {addDestroyEffects(effectRenderer)}
@SideOnly(Side.CLIENT)
@Deprecated
def addDestroyEffects(effectRenderer:EffectRenderer){}
/**
* Render the static, unmoving faces of this part into the world renderer.
* The Tessellator is already drawing.
* @param olm An optional light matrix to be used for rendering things with perfect MC blended lighting (eg microblocks). Only use this if you have to.
* @param pass The render pass, 1 or 0
*/
@SideOnly(Side.CLIENT)
def renderStatic(pos:Vector3, olm:LazyLightMatrix, pass:Int){}
/**
* Render the dynamic, changing faces of this part and other gfx as in a TESR.
* The Tessellator will need to be started if it is to be used.
* @param pos The position of this block space relative to the renderer, same as x, y, z passed to TESR.
* @param frame The partial interpolation frame value for animations between ticks
* @param pass The render pass, 1 or 0
*/
@SideOnly(Side.CLIENT)
def renderDynamic(pos:Vector3, frame:Float, pass:Int){}
/**
* Draw the breaking overlay for this part. The overrideIcon in RenderBlocks will be set to the fracture icon.
*/
@SideOnly(Side.CLIENT)
def drawBreaking(renderBlocks:RenderBlocks){}
/**
* Override the drawing of the selection box around this part.
* @param hit An instance of ExtendedMOP from collisionRayTrace
* @return true if highlight rendering was overridden.
*/
@SideOnly(Side.CLIENT)
def drawHighlight(hit:MovingObjectPosition, player:EntityPlayer, frame:Float):Boolean = false
/**
* Write all the data required to describe a client version of this part to the packet.
* Called serverside, when a client loads this part for the first time.
*/
def writeDesc(packet:MCDataOutput){}
/**
* Fill out this part with the description information contained in packet. Will be exactly as written from writeDesc.
* Called clientside when a client loads this part for the first time.
*/
def readDesc(packet:MCDataInput){}
/**
* Save part to NBT (only called serverside)
*/
def save(tag:NBTTagCompound){}
/**
* Load part from NBT (only called serverside)
*/
def load(tag:NBTTagCompound){}
/**
* Gets a MCDataOutput instance for writing update data to clients with this part loaded.
* The write stream functions as a buffer which is flushed in a compressed databurst packet at the end of the tick.
*/
def getWriteStream:MCDataOutput = tile.getWriteStream(this)
/**
* Read and operate on data written to getWriteStream. Ensure all data this part wrote is read even if it's not going to be used.
* The default implementation assumes a call to sendDescUpdate as the only use of getWriteStream.
*/
def read(packet:MCDataInput)
{
readDesc(packet)
tile.markRender()
}
/**
* Quick and easy method to re-describe the whole part on the client. This will call read on the client which calls readDesc unless overriden.
* Incremental changes should be sent rather than the whole description packet if possible.
*/
def sendDescUpdate() = writeDesc(getWriteStream)
/**
* Called when a part is added or removed from this block space.
* The part parameter may be null if several things have changed.
*/
def onPartChanged(part:TMultiPart){}
/**
* Called when a neighbor block changed
*/
def onNeighborChanged(){}
/**
* Called when this part is added to the block space
*/
def onAdded() = onWorldJoin()
/**
* Called when this part is removed from the block space
*/
def onRemoved() = onWorldSeparate()
/**
* Called when the containing chunk is loaded on the server.
*/
def onChunkLoad() = onWorldJoin()
/**
* Called when the containing chunk is unloaded on the server.
*/
def onChunkUnload() = onWorldSeparate()
/**
* Called when this part separates from the world (due to removal, chunk unload or other). Use this to sync with external data structures.
*/
def onWorldSeparate(){}
/**
* Called when this part separates from the world (due to removal, chunk unload or other). Use this to sync with external data structures.
*/
def onWorldJoin(){}
/**
* Called when this part is converted from a normal block/tile (only applicable if a converter has been registered)
*/
def onConverted() = onAdded()
/**
* Called when this part is converted from a normal block/tile (only applicable if a converter has been registered) before the original tile has been replaced
* Use this to clear out things like inventory from the old tile.
*/
def invalidateConvertedTile(){}
/**
* Called when this part has been moved without a save/load.
*/
def onMoved() = onWorldJoin()
/**
* Called just before this part is actually removed from the container tile
*/
def preRemove(){}
/**
* Return whether this part needs update ticks. This will only be called on addition/removal so it should be a constant for this instance.
*/
def doesTick = true
/**
* Called once per world tick. This will be called even if doesTick returns false if another part in the space needs ticks.
*/
def update(){}
/**
* Called when a scheduled tick is executed.
*/
def scheduledTick(){}
/**
* Sets a scheduledTick callback for this part ticks in the future. This is a world time value, so if the chunk is unloaded and reloaded some time later, the tick may fire immediately.
*/
def scheduleTick(ticks:Int) = TickScheduler.scheduleTick(this, ticks)
/**
* Return the itemstack for the middle click pick-block function.
*/
def pickItem(hit:MovingObjectPosition):ItemStack = null
/**
* Called on block right click. item is the player's held item.
* This should not modify the part client side. If the client call returns false, the server will not call this function.
* @param hit An instance of ExtendedMOP from collisionRayTrace
*/
def activate(player:EntityPlayer, hit:MovingObjectPosition, item:ItemStack) = false
/**
* Called on block left click. item is the player's held item.
* @param hit An instance of ExtendedMOP from collisionRayTrace
*/
def click(player:EntityPlayer, hit:MovingObjectPosition, item:ItemStack){}
/**
* Called when an entity is within this block space. May not actually collide with this part.
*/
def onEntityCollision(entity:Entity){}
}

View file

@ -0,0 +1,68 @@
package codechicken.multipart
import codechicken.lib.vec.Cuboid6
import scala.collection.JavaConversions._
import java.lang.Iterable
/**
* This suite of 3 classes provides simple functions for standard bounding box based occlusion testing.
* If any two parts have overlapping bounding boxes, the test fails
*
* See TIconHitEffects for notes on the Scala|Java composition setup.
*/
object NormalOcclusionTest
{
/**
* Performs the test, returns true if the test fails
*/
def apply(boxes1:Traversable[Cuboid6], boxes2:Traversable[Cuboid6]):Boolean =
boxes1.forall(v1 => boxes2.forall(v2 => !v1.intersects(v2)))
/**
* Performs the test, returns true if the test fails
*/
def apply(part1:JNormalOcclusion, part2:TMultiPart):Boolean =
{
var boxes = Seq[Cuboid6]()
if(part2.isInstanceOf[JNormalOcclusion])
boxes = boxes++part2.asInstanceOf[JNormalOcclusion].getOcclusionBoxes
if(part2.isInstanceOf[JPartialOcclusion])
boxes = boxes++part2.asInstanceOf[JPartialOcclusion].getPartialOcclusionBoxes
return NormalOcclusionTest(boxes, part1.getOcclusionBoxes)
}
}
/**
* Java interface containing callbacks for normal occlusion testing.
* Make sure to override occlusionTest as in TNormalOcclusion
*/
trait JNormalOcclusion
{
/**
* Return a list of normal occlusion boxes
*/
def getOcclusionBoxes:Iterable[Cuboid6]
}
/**
* Trait for scala programmers
*/
trait TNormalOcclusion extends TMultiPart with JNormalOcclusion
{
override def occlusionTest(npart:TMultiPart):Boolean =
NormalOcclusionTest(this, npart) && super.occlusionTest(npart)
}
/**
* Utility part class for performing 3rd party occlusion tests
*/
class NormallyOccludedPart(bounds:Iterable[Cuboid6]) extends TMultiPart with TNormalOcclusion
{
def this(bound:Cuboid6) = this(Seq(bound))
def getType = null
def getOcclusionBoxes = bounds
}

View file

@ -0,0 +1,76 @@
package codechicken.multipart
import codechicken.lib.vec.Cuboid6
import scala.collection.JavaConversions._
import java.lang.Iterable
/**
* This class provides a special type of occlusion model used by microblocks.
* The partial occlusion test defines bounding boxes that may intersect, so long as no part is completely obscured by a combination of the others.
* Partial bounding boxes may not intersect with normal bounding boxes from NormalOcclusionTest
*
* This test is actually managed by the mixin trait TPartialOcclusionTile which is generated when the marker interface JPartialOcclusion is found
*/
class PartialOcclusionTest(size:Int)
{
/**
* The resolution of the test, set to 1/8th of a block
*/
val res = 8
val bits = new Array[Byte](res*res*res)
val partial = new Array[Boolean](size)
def fill(i:Int, part:JPartialOcclusion)
{
fill(i, part.getPartialOcclusionBoxes, part.allowCompleteOcclusion)
}
def fill(i:Int, boxes:Iterable[Cuboid6], complete:Boolean)
{
partial(i) = !complete
boxes.foreach(box => fill(i+1, box))
}
def fill(v:Int, box:Cuboid6)
{
for(x <- (box.min.x*res+0.5).toInt until (box.max.x*res+0.5).toInt)
for(y <- (box.min.y*res+0.5).toInt until (box.max.y*res+0.5).toInt)
for(z <- (box.min.z*res+0.5).toInt until (box.max.z*res+0.5).toInt)
{
val i = (x*res+y)*res+z
if(bits(i) == 0)
bits(i) = v.toByte
else
bits(i) = -1
}
}
def apply():Boolean =
{
val visible = new Array[Boolean](size)
bits.foreach(n => if(n > 0) visible(n-1) = true)
var i = 0
while(i < partial.length)
{
if(partial(i) && !visible(i))
return false
i+=1
}
return true
}
}
trait JPartialOcclusion
{
/**
* Return a list of partial occlusion boxes
*/
def getPartialOcclusionBoxes:Iterable[Cuboid6]
/**
* Return true if this part may be completely obscured
*/
def allowCompleteOcclusion = false
}

View file

@ -0,0 +1,15 @@
package codechicken.multipart
/**
* Interface for parts that fill a slot based configuration as defined in PartMap.
* If this is implemented, calling partMap(slot) on the host tile will return this part if the corresponding bit in the slotMask is set
*
* Marker interface for TSlottedTile
*/
trait TSlottedPart extends TMultiPart
{
/**
* a bitmask of slots that this part fills. slot x is 1<<x
*/
def getSlotMask:Int
}

View file

@ -0,0 +1,281 @@
package codechicken.multipart
import codechicken.lib.world.WorldExtensionInstantiator
import codechicken.lib.world.WorldExtension
import codechicken.lib.world.ChunkExtension
import net.minecraft.world.chunk.Chunk
import net.minecraft.world.World
import net.minecraft.nbt.NBTTagCompound
import scala.collection.mutable.ListBuffer
import net.minecraft.nbt.NBTTagList
import codechicken.lib.vec.BlockCoord
import net.minecraft.world.ChunkCoordIntPair
import net.minecraft.world.ChunkPosition
import scala.collection.mutable.HashSet
import java.io.DataOutputStream
import net.minecraftforge.common.DimensionManager
import java.io.File
import java.io.FileOutputStream
import net.minecraft.nbt.CompressedStreamTools
import java.io.DataInputStream
import java.io.FileInputStream
import net.minecraft.world.storage.SaveHandler
import java.util.ArrayList
/**
* Used for scheduling delayed callbacks to parts.
* Do not use this for redstone applications that require precise timing.
* If 2 parts are both scheduled for an update on the same tick, there is no guarantee which one will update first.
* These parts should not depend on a state of another part that may have changed before/after them.
*/
object TickScheduler extends WorldExtensionInstantiator
{
class PartTickEntry(val part:TMultiPart, var time:Long, var random:Boolean)
{
def this(part:TMultiPart, ticks:Int) = this(part, ticks, false)
}
private class WorldTickScheduler(world$:World) extends WorldExtension(world$)
{
var schedTime = 0L
var tickChunks = HashSet[ChunkTickScheduler]()
private var processing = false
private val pending = ListBuffer[PartTickEntry]()
def scheduleTick(part:TMultiPart, ticks:Int, random:Boolean)
{
if(processing)
pending+=new PartTickEntry(part, schedTime+ticks, random)
else
_scheduleTick(part, schedTime+ticks, random)
}
def _scheduleTick(part:TMultiPart, time:Long, random:Boolean)
{
if(part.tile != null)
getChunkExtension(part.tile.xCoord>>4, part.tile.zCoord>>4)
.asInstanceOf[ChunkTickScheduler].scheduleTick(part, time, random)
}
def loadRandom(part:TMultiPart) = scheduleTick(part, nextRandomTick, true)
override def preTick()
{
processing = true
}
override def postTick()
{
if(!tickChunks.isEmpty)
tickChunks = tickChunks.filter(_.processTicks())
processing = false
pending.foreach(e => _scheduleTick(e.part, e.time, e.random))
pending.clear()
schedTime+=1
}
def saveDir:File =
{
if(world.provider.dimensionId == 0)//Calling DimensionManager.getCurrentSaveRootDirectory too early breaks game saves, we have a world reference, use it
return world.getSaveHandler.asInstanceOf[SaveHandler].getWorldDirectory
return new File(DimensionManager.getCurrentSaveRootDirectory, world.provider.getSaveFolder)
}
def saveFile:File = new File(saveDir, "multipart.dat")
override def load()
{
try
{
val din = new DataInputStream(new FileInputStream(saveFile))
loadTag(CompressedStreamTools.readCompressed(din))
din.close()
}
catch
{
case e:Exception =>
}
loadTag(new NBTTagCompound)
}
def loadTag(tag:NBTTagCompound)
{
if(tag.hasKey("schedTime"))
schedTime = tag.getLong("schedTime")
else
schedTime = world.getTotalWorldTime
}
def saveTag:NBTTagCompound =
{
val tag = new NBTTagCompound
tag.setLong("schedTime", schedTime)
return tag
}
override def save() {
val file = saveFile
if(!file.getParentFile.exists)
file.getParentFile.mkdirs()
if(!file.exists)
file.createNewFile()
val dout = new DataOutputStream(new FileOutputStream(file))
CompressedStreamTools.writeCompressed(saveTag, dout)
dout.close()
}
def nextRandomTick = world.rand.nextInt(800)+800
}
def createWorldExtension(world:World):WorldExtension = new WorldTickScheduler(world)
private class ChunkTickScheduler(chunk$:Chunk, world:WorldTickScheduler) extends ChunkExtension(chunk$, world)
{
import codechicken.multipart.handler.MultipartProxy._
var tickList = ListBuffer[PartTickEntry]()
def schedTime = world.schedTime
def scheduleTick(part:TMultiPart, time:Long, random:Boolean)
{
val it = tickList.iterator
while(it.hasNext)
{
val e = it.next()
if(e.part == part)
{
if(e.random && !random)//only override an existing tick if we're going from random->scheduled
{
e.time = time
e.random = random
}
return
}
}
tickList+=new PartTickEntry(part, time, random)
if(tickList.size == 1)
world.tickChunks+=this
}
def nextRandomTick = world.nextRandomTick
def processTicks():Boolean =
{
tickList = tickList.filter(processTick)
return !tickList.isEmpty
}
def processTick(e:PartTickEntry):Boolean =
{
if(e.time <= schedTime)
{
if(e.part.tile != null)
{
if(e.random && e.part.isInstanceOf[IRandomUpdateTick])
e.part.asInstanceOf[IRandomUpdateTick].randomUpdate()
else
e.part.scheduledTick()
if(e.part.isInstanceOf[IRandomUpdateTick])
{
e.time = schedTime+nextRandomTick
e.random = true
return true
}
}
return false
}
return true
}
override def saveData(data:NBTTagCompound)
{
val tagList = new NBTTagList
tickList.foreach{e =>
val part = e.part
if(part.tile != null && !e.random)
{
val tag = new NBTTagCompound
tag.setShort("pos", indexInChunk(new BlockCoord(part.tile)).toShort)
tag.setByte("i", part.tile.partList.indexOf(part).toByte)
tag.setLong("time", e.time)
tagList.appendTag(tag)
}
}
if(tagList.tagCount > 0)
data.setTag("multipartTicks", tagList)
}
override def loadData(data:NBTTagCompound)
{
tickList.clear()
if(!data.hasKey("multipartTicks"))
return
val tagList = data.getTagList("multipartTicks")
val cc = new ChunkCoordIntPair(0, 0)
for(i <- 0 until tagList.tagCount)
{
val tag = tagList.tagAt(i).asInstanceOf[NBTTagCompound]
val pos = indexInChunk(cc, tag.getShort("pos"))
val tile = chunk.chunkTileEntityMap.get(new ChunkPosition(pos.x, pos.y, pos.z))
if(tile.isInstanceOf[TileMultipart])
tickList+=new PartTickEntry(tile.asInstanceOf[TileMultipart].partList(tag.getByte("i")), tag.getLong("time"), false)
}
}
override def load()
{
val it = new ArrayList(chunk.chunkTileEntityMap.values).iterator
while(it.hasNext)
{
val t = it.next
if(t.isInstanceOf[TileMultipart])
{
val tmp = t.asInstanceOf[TileMultipart]
tmp.onChunkLoad()
tmp.partList.foreach(p =>
if(p.isInstanceOf[IRandomUpdateTick])
world.scheduleTick(p, nextRandomTick, true))
}
}
if(!tickList.isEmpty)
world.tickChunks+=this
}
override def unload()
{
if(!tickList.isEmpty)
world.tickChunks-=this
}
}
def createChunkExtension(chunk:Chunk, world:WorldExtension):ChunkExtension = new ChunkTickScheduler(chunk, world.asInstanceOf[WorldTickScheduler])
private[multipart] def loadRandomTick(part:TMultiPart)
{
getExtension(part.tile.worldObj).asInstanceOf[WorldTickScheduler].loadRandom(part)
}
/**
* Schedule a tick for part relative to the current time.
*/
def scheduleTick(part:TMultiPart, ticks:Int)
{
getExtension(part.tile.worldObj).asInstanceOf[WorldTickScheduler].scheduleTick(part, ticks, false)
}
/**
* Returns the current scheduler time. Like the world time, but unaffected by the time set command and other things changing time of day.
* Deprecated in favor of world.getTotalWorldTime
*/
@Deprecated
def getSchedulerTime(world:World):Long = getExtension(world).asInstanceOf[WorldTickScheduler].schedTime
}

View file

@ -0,0 +1,699 @@
package codechicken.multipart
import net.minecraft.tileentity.TileEntity
import scala.collection.mutable.ListBuffer
import codechicken.lib.packet.PacketCustom
import codechicken.lib.vec.BlockCoord
import net.minecraft.world.World
import java.util.List
import net.minecraft.nbt.NBTTagCompound
import codechicken.lib.data.MCDataOutput
import codechicken.multipart.handler.MultipartProxy
import net.minecraft.block.Block
import net.minecraft.item.ItemStack
import codechicken.lib.vec.Vector3
import net.minecraft.nbt.NBTTagList
import java.util.Random
import codechicken.multipart.handler.MultipartSPH
import codechicken.lib.lighting.LazyLightMatrix
import net.minecraft.entity.item.EntityItem
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.entity.Entity
import scala.collection.JavaConversions._
import java.util.Collection
import codechicken.lib.raytracer.ExtendedMOP
import net.minecraft.util.Vec3
import java.lang.Iterable
import scala.collection.mutable.{Map => MMap}
import net.minecraft.util.AxisAlignedBB
class TileMultipart extends TileEntity
{
/**
* List of parts in this tile space
*/
var partList = Seq[TMultiPart]()
private var doesTick = false
private[multipart] def from(that:TileMultipart)
{
copyFrom(that)
loadFrom(that)
}
/**
* This method should be used for copying all the data from the fields in that container tile.
* This method will be automatically generated on java tile traits with fields if it is not overridden.
*/
def copyFrom(that:TileMultipart)
{
partList = that.partList
doesTick = that.doesTick
}
def loadFrom(that:TileMultipart)
{
partList.foreach(_.bind(this))
if(doesTick)
{
doesTick = false
setTicking(true)
}
}
/**
* Overidden in TSlottedTile when a part that goes in a slot is added
*/
def partMap(slot:Int):TMultiPart = null
/**
* Implicit java conversion of part list
*/
def jPartList():List[TMultiPart] = partList
override def canUpdate = doesTick
def operate(f:(TMultiPart)=>Unit) {
val it = partList.iterator
while(it.hasNext) {
val p = it.next()
if(p.tile != null) f(p)
}
}
override def updateEntity()
{
operate(_.update())
}
override def onChunkUnload()
{
operate(_.onChunkUnload())
}
def onChunkLoad()
{
operate(_.onChunkLoad())
}
final def setValid(b:Boolean)
{
if(b)
super.validate()
else
super.invalidate()
}
def onMoved()
{
operate(_.onMoved())
}
override def invalidate()
{
if(!isInvalid)
{
super.invalidate()
if(worldObj != null) {
partList.foreach(_.onWorldSeparate())
if(worldObj.isRemote)
TileMultipart.putClientCache(this)
}
}
}
/**
* Called by parts when they have changed in some form that affects the world.
* Notifies neighbor blocks, parts that share this host and recalculates lighting
*/
def notifyPartChange(part:TMultiPart)
{
internalPartChange(part)
worldObj.notifyBlocksOfNeighborChange(xCoord, yCoord, zCoord, MultipartProxy.block.blockID)
worldObj.updateAllLightTypes(xCoord, yCoord, zCoord)
}
/**
* Notifies parts sharing this host of a change
*/
def internalPartChange(part:TMultiPart)
{
operate(p => if(part != p) p.onPartChanged(part))
}
/**
* Notifies all parts not in the passed collection of a change from all the parts in the collection
*/
def multiPartChange(parts:Collection[TMultiPart])
{
operate(p => if(!parts.contains(p)) parts.foreach(p.onPartChanged))
}
/**
* Notifies neighboring blocks that this tile has changed
*/
def notifyTileChange()
{
worldObj.func_96440_m(xCoord, yCoord, zCoord, 0)
}
def onNeighborBlockChange()
{
operate(_.onNeighborChanged())
}
/**
* Blank implementation, overriden by TTileChangeTile
*/
def onNeighborTileChange(tileX:Int, tileY:Int, tileZ:Int) {}
def getLightValue = partList.view.map(_.getLightValue).max
def getExplosionResistance(entity:Entity) = partList.view.map(_.explosionResistance(entity)).max
/**
* Callback for parts to mark the chunk as needs saving
*/
def markDirty()
{
worldObj.markTileEntityChunkModified(xCoord, yCoord, zCoord, this)
}
/**
* Mark this block space for a render update.
*/
def markRender()
{
worldObj.markBlockForRenderUpdate(xCoord, yCoord, zCoord)
}
/**
* Helper function for calling a second level notify on a side (eg indirect power from a lever)
*/
def notifyNeighborChange(side:Int)
{
val pos = new BlockCoord(this).offset(side)
worldObj.notifyBlocksOfNeighborChange(pos.x, pos.y, pos.z, MultipartProxy.block.blockID)
}
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 && worldObj.getBlockTileEntity(xCoord, yCoord, zCoord) == this)
{
if(tick)
worldObj.addTileEntity(this)
else
worldObj.markTileEntityForDespawn(this)
}
}
/**
* Returns true if part can be added to this space
*/
def canAddPart(part:TMultiPart):Boolean =
{
if(partList.contains(part))
return false
return occlusionTest(partList, part)
}
/**
* Returns true if opart can be replaced with npart (note opart and npart may be the exact same object)
*
* This function should be used for testing if a part can change it's shape (eg. rotation, expansion, cable connection)
* For example, to test whether a cable part can connect to it's neighbor:
* 1. Set the cable part's bounding boxes as if the connection is established
* 2. Call canReplacePart(part, part)
* 3. If canReplacePart succeeds, perform connection, else, revert bounding box
*/
def canReplacePart(opart:TMultiPart, npart:TMultiPart):Boolean =
{
val olist = partList.filterNot(_ == opart)
if(olist.contains(npart))
return false
return occlusionTest(olist, npart)
}
/**
* Returns true if parts do not occlude npart
*/
def occlusionTest(parts:Seq[TMultiPart], npart:TMultiPart):Boolean =
{
return parts.forall(part => part.occlusionTest(npart) && npart.occlusionTest(part))
}
/**
* Get the write stream for updates to part
*/
def getWriteStream(part:TMultiPart):MCDataOutput = getWriteStream.writeByte(partList.indexOf(part))
private def getWriteStream = MultipartSPH.getTileStream(worldObj, new BlockCoord(this))
private[multipart] def addPart_impl(part:TMultiPart)
{
if(!worldObj.isRemote)
writeAddPart(part)
addPart_do(part)
part.onAdded()
partAdded(part)
notifyPartChange(part)
notifyTileChange()
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)
{
assert(partList.size < 250, "Tried to add more than 250 parts to the one tile. You're doing it wrong")
partList = partList:+part
bindPart(part)
part.bind(this)
if(!doesTick && part.doesTick)
setTicking(true)
}
/**
* Bind this part to an internal cache.
* Provided for trait overrides, do not call externally.
*/
def bindPart(part:TMultiPart){}
/**
* Called when a part is added (placement)
* Provided for trait overrides, do not call externally.
*/
def partAdded(part:TMultiPart)
{
if(part.isInstanceOf[IRandomUpdateTick])
TickScheduler.loadRandomTick(part)
}
/**
* Removes part from this tile. Note that due to the operation sync, the part may not be removed until the call stack has been passed to all other parts in the space.
*/
def remPart(part:TMultiPart):TileMultipart =
{
assert(!worldObj.isRemote, "Cannot remove multi parts from a client tile")
remPart_impl(part)
}
private[multipart] def remPart_impl(part:TMultiPart):TileMultipart =
{
remPart_do(part, !worldObj.isRemote)
if(!isInvalid)
{
val tile = MultipartGenerator.partRemoved(this, part)
notifyPartChange(part)
markDirty()
markRender()
return tile
}
return null
}
private def remPart_do(part:TMultiPart, sendPacket:Boolean):Int =
{
val r = partList.indexOf(part)
if(r < 0)
throw new IllegalArgumentException("Tried to remove a non-existant part")
part.preRemove()
partList = partList.filterNot(_ == part)
if(sendPacket)
getWriteStream.writeByte(254).writeByte(r)
partRemoved(part, r)
part.onRemoved()
part.tile = null
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
}
/**
* Remove this part from internal cache.
* Provided for trait overrides, do not call externally.
*/
def partRemoved(part:TMultiPart, p:Int){}
private[multipart] def loadParts(parts:ListBuffer[TMultiPart])
{
clearParts()
parts.foreach(p => addPart_do(p))
if(worldObj != null) {
if(worldObj.isRemote)
operate(_.onWorldJoin())
notifyPartChange(null)
}
}
/**
* Remove all parts from internal cache
* Provided for trait overrides, do not call externally.
*/
def clearParts()
{
partList = Seq()
}
/**
* Writes the description of this tile, and all parts composing it, to packet
*/
def writeDesc(packet:MCDataOutput)
{
packet.writeByte(partList.size)
partList.foreach{part =>
MultiPartRegistry.writePartID(packet, part)
part.writeDesc(packet)
}
}
/**
* Perform a raytrace returning all intersecting parts sorted nearest to farthest
*/
def rayTraceAll(start:Vec3, end:Vec3):Iterable[ExtendedMOP] =
{
var list = ListBuffer[ExtendedMOP]()
for((p, i) <- partList.view.zipWithIndex)
p.collisionRayTrace(start, end) match {
case mop:ExtendedMOP =>
mop.data = (i, mop.data)
list+=mop
case _ =>
}
return list.sorted
}
/**
* Perform a raytrace returning the nearest intersecting part
*/
def collisionRayTrace(start:Vec3, end:Vec3):ExtendedMOP = rayTraceAll(start, end).headOption.getOrElse(null)
/**
* Drop and remove part at index (internal mining callback)
*/
def harvestPart(index:Int, hit:ExtendedMOP, player:EntityPlayer) = partList(index) match {
case null =>
case part => part.harvest(hit, player)
}
/**
* Utility function for dropping items around the center of this space
*/
def dropItems(items:Iterable[ItemStack])
{
val pos = Vector3.fromTileEntityCenter(this)
items.foreach(item => TileMultipart.dropItem(item, worldObj, pos))
}
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)
}
/**
* Internal callback
*/
def onEntityCollision(entity:Entity)
{
operate(_.onEntityCollision(entity))
}
/**
* Internal callback, overriden in TRedstoneTile
*/
def strongPowerLevel(side:Int) = 0
/**
* Internal callback, overriden in TRedstoneTile
*/
def weakPowerLevel(side:Int) = 0
/**
* Internal callback, overriden in TRedstoneTile
*/
def canConnectRedstone(side:Int) = false
}
class 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, pass:Int)
{
partList.foreach(part => part.renderDynamic(pos, frame, pass:Int))
}
def randomDisplayTick(random:Random){}
override def shouldRenderInPass(pass:Int) =
{
MultipartRenderer.pass = pass
true
}
override def getRenderBoundingBox = AxisAlignedBB.getAABBPool.getAABB(xCoord, yCoord, zCoord, xCoord + 1, yCoord + 1, zCoord + 1)
}
/**
* Static class with multipart manipulation helper functions
*/
object TileMultipart
{
var renderID:Int = -1
/**
* Playerinstance will often remove the tile entity instance and set the block to air on the client before the multipart packet handler fires it's updates.
* In order to maintain the packet data, and make sure all written data is read, the tile needs to be kept around internally until
*/
private val clientFlushMap = MMap[BlockCoord, TileMultipart]()
private[multipart] def flushClientCache() = clientFlushMap.clear()
private[multipart] def putClientCache(t:TileMultipart) = clientFlushMap.put(new BlockCoord(t), t)
/**
* Gets a multipar ttile instance at pos, converting if necessary.
*/
def getOrConvertTile(world:World, pos:BlockCoord) = getOrConvertTile2(world, pos)._1
/**
* Gets a multipart tile instance at pos, converting if necessary.
* Note converted tiles are merely a structure formality,
* they do not actually exist in world until they are required to by the addition of another multipart to their space.
* @return (The tile or null if there was none, true if the tile is a result of a conversion)
*/
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)
}
/**
* Gets the multipart tile instance at pos, or null if it doesn't exist or is not a multipart tile
*/
def getTile(world:World, pos:BlockCoord) =
world.getBlockTileEntity(pos.x, pos.y, pos.z) match {
case t:TileMultipart => t
case _ => null
}
/**
* Returns whether part can be added to the space at pos. Will do conversions as necessary.
* This function is the recommended way to add parts to the world.
*/
def canPlacePart(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
}
/**
* Returns if the block at pos is replaceable (air, vines etc)
*/
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)
}
/**
* Adds a part to a block space. canPlacePart should always be called first.
* The addition of parts on the client is handled internally.
*/
def addPart(world:World, pos:BlockCoord, part:TMultiPart):TileMultipart =
{
assert(!world.isRemote, "Cannot add multi parts to a client tile.")
return MultipartGenerator.addPart(world, pos, part)
}
/**
* Constructs this tile and its parts from a desc packet
*/
def handleDescPacket(world:World, pos:BlockCoord, packet:PacketCustom)
{
val nparts = packet.readUByte
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.setBlock(pos.x, pos.y, pos.z, MultipartProxy.block.blockID)
MultipartGenerator.silentAddTile(world, pos, tilemp)
}
tilemp.loadParts(parts)
tilemp.notifyTileChange()
tilemp.markRender()
}
/**
* Handles an update packet, addition, removal and otherwise
*/
def handlePacket(pos:BlockCoord, world:World, i:Int, packet:PacketCustom)
{
lazy val tilemp = Option(BlockMultipart.getTile(world, pos.x, pos.y, pos.z)).getOrElse(clientFlushMap(pos))
i match
{
case 253 =>
val part = MultiPartRegistry.readPart(packet)
part.readDesc(packet)
MultipartGenerator.addPart(world, pos, part)
case 254 => tilemp.remPart_impl(tilemp.partList(packet.readUByte))
case _ => tilemp.partList(i).read(packet)
}
}
/**
* Creates this tile from an NBT tag
*/
def createFromNBT(tag:NBTTagCompound):TileMultipart =
{
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
}
/**
* Drops an item around pos
*/
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.2
item.motionZ = world.rand.nextGaussian() * 0.05
item.delayBeforeCanPickup = 10
world.spawnEntityInWorld(item)
}
}

View file

@ -0,0 +1,623 @@
package codechicken.multipart.asm
import scala.collection.mutable.{Map => MMap, ListBuffer => MList, Set => MSet}
import java.util.{Set => JSet}
import scala.collection.JavaConversions._
import org.objectweb.asm.tree._
import org.objectweb.asm.Opcodes._
import org.objectweb.asm.ClassReader
import org.objectweb.asm.util.TraceClassVisitor
import org.objectweb.asm.util.Textifier
import org.objectweb.asm.Type
import org.objectweb.asm.MethodVisitor
import Type._
import codechicken.lib.asm.ASMHelper._
import codechicken.lib.asm.ObfMapping
import java.io.File
import java.io.PrintWriter
import ScalaSignature._
import java.lang.reflect.Method
import java.lang.reflect.Modifier
import net.minecraft.launchwrapper.LaunchClassLoader
import codechicken.multipart.handler.MultipartProxy
import cpw.mods.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper
object DebugPrinter
{
val debug = MultipartProxy.config.getTag("debug_asm").getBooleanValue(!ObfMapping.obfuscated)
private var permGenUsed = 0
val dir = new File("asm/multipart")
if(debug)
{
if(!dir.exists)
dir.mkdirs()
for(file <- dir.listFiles)
file.delete
}
def dump(name:String, bytes:Array[Byte])
{
if(!debug) return
val fileout = new File(dir, name.replace('/', '#')+".txt")
val pout = new PrintWriter(fileout)
new ClassReader(bytes).accept(new TraceClassVisitor(null, new Textifier(), pout), 0)
pout.close()
}
def defined(name:String, bytes:Array[Byte])
{
if((permGenUsed+bytes.length)/16000 != permGenUsed/16000)
log((permGenUsed+bytes.length)+" bytes of permGen has been used by ASMMixinCompiler")
permGenUsed += bytes.length
}
def log(msg:String) = if(debug) println(msg)
}
object ASMMixinCompiler
{
import StackAnalyser.width
val cl = getClass.getClassLoader.asInstanceOf[LaunchClassLoader]
val m_defineClass = classOf[ClassLoader].getDeclaredMethod("defineClass", classOf[Array[Byte]], Integer.TYPE, Integer.TYPE)
val m_runTransformers = classOf[LaunchClassLoader].getDeclaredMethod("runTransformers", classOf[String], classOf[String], classOf[Array[Byte]])
val f_transformerExceptions = classOf[LaunchClassLoader].getDeclaredField("transformerExceptions")
m_defineClass.setAccessible(true)
m_runTransformers.setAccessible(true)
f_transformerExceptions.setAccessible(true)
private val traitByteMap = MMap[String, Array[Byte]]()
private val mixinMap = MMap[String, MixinInfo]()
def define(name:String, bytes:Array[Byte]) =
{
internalDefine(name, bytes)
DebugPrinter.defined(name, bytes)
try {
m_defineClass.invoke(cl, bytes, 0:Integer, bytes.length:Integer).asInstanceOf[Class[_]]
} catch {
case link:LinkageError if link.getMessage.contains("duplicate") =>
throw new IllegalStateException("class with name: "+name+" already loaded. Do not reference your mixin classes before registering them with MultipartGenerator", link)
}
}
getBytes("cpw/mods/fml/common/asm/FMLSanityChecker")
def getBytes(name:String):Array[Byte] =
{
val jName = name.replace('/', '.')
if(jName.equals("java.lang.Object"))
return null
def useTransformers = f_transformerExceptions.get(cl).asInstanceOf[JSet[String]]
.find(jName.startsWith).isEmpty
val obfName = FMLDeobfuscatingRemapper.INSTANCE.unmap(name).replace('/', '.')
val bytes = cl.getClassBytes(obfName)
if(bytes != null && useTransformers)
return m_runTransformers.invoke(cl, jName, obfName, bytes).asInstanceOf[Array[Byte]]
return bytes
}
def internalDefine(name:String, bytes:Array[Byte])
{
traitByteMap.put(name.replace('.', '/'), bytes)
BaseNodeInfo.clear(name)
DebugPrinter.dump(name, bytes)
}
def classNode(name:String) = traitByteMap.getOrElseUpdate(name.replace('.', '/'), getBytes(name.replace('.', '/'))) match {
case null => null
case v => createClassNode(v, ClassReader.EXPAND_FRAMES)
}
def isScala(cnode:ClassNode) = ScalaSigReader.ann(cnode).isDefined
def isTrait(cnode:ClassNode) =
{
val csym:ClassSymbol = ScalaSigReader.read(ScalaSigReader.ann(cnode).get).evalT(0)
csym.isTrait && !csym.isInterface
}
def getMixinInfo(name:String) = mixinMap.get(name)
case class FieldMixin(name:String, desc:String, access:Int)
{
def accessName(owner:String) = if((access & ACC_PRIVATE) != 0)
owner.replace('/', '$')+"$$"+name
else
name
}
case class MixinInfo(name:String, parent:String, fields:Seq[FieldMixin],
methods:Seq[MethodNode], supers:Map[String, String])
{
def tname = name+"$class"
}
object BaseNodeInfo
{
private val baseNodeMap = MMap[String, BaseNodeInfo]()
def clear(name:String) = baseNodeMap.remove(name)
def getNodeInfo(name:String) = baseNodeMap.getOrElseUpdate(name, new BaseNodeInfo(name))
private def getNodeInfo(clazz:ClassInfoSource) = clazz match {
case null => null
case v => baseNodeMap.getOrElseUpdate(v.name, new BaseNodeInfo(v))
}
case class MethodNodeInfo(owner:String, m:MethodInfoSource)
{
def name = m.name
def desc = m.desc
}
trait MethodInfoSource
{
def name:String
def desc:String
def isPrivate:Boolean
def isAbstract:Boolean
}
trait ClassInfoSource
{
def name:String
def superClass:Option[BaseNodeInfo]
def interfaces:Seq[BaseNodeInfo]
def methods:Seq[MethodInfoSource]
}
implicit def JClassInfoSource(clazz:Class[_]) = if(clazz == null) null else new JClassInfoSource(clazz)
class JClassInfoSource(clazz:Class[_]) extends ClassInfoSource
{
case class JMethodInfoSource(method:Method) extends MethodInfoSource
{
def name = method.getName
def desc = getType(method).getDescriptor
def isPrivate = Modifier.isPrivate(method.getModifiers)
def isAbstract = Modifier.isAbstract(method.getModifiers)
}
def name = clazz.getName.replace('.', '/')
def superClass = Option(getNodeInfo(clazz.getSuperclass))
def interfaces = clazz.getInterfaces.map(getNodeInfo(_))
def methods = clazz.getMethods.map(JMethodInfoSource(_))
}
implicit def ClassNodeInfoSource(cnode:ClassNode) = if(cnode == null) null else new ClassNodeInfoSource(cnode)
class ClassNodeInfoSource(cnode:ClassNode) extends ClassInfoSource
{
case class MethodNodeInfoSource(mnode:MethodNode) extends MethodInfoSource
{
def name = mnode.name
def desc = mnode.desc
def isPrivate = (mnode.access & ACC_PRIVATE) != 0
def isAbstract = (mnode.access & ACC_ABSTRACT) != 0
}
def name = cnode.name
def superClass = Option(getNodeInfo(cnode.superName))
def interfaces = cnode.interfaces match {
case null => Seq()
case v => v.map(getNodeInfo)
}
def methods = cnode.methods.map(MethodNodeInfoSource(_))
}
class ScalaNodeInfoSource(cnode:ClassNode) extends ClassNodeInfoSource(cnode)
{
val sig = ScalaSigReader.read(ScalaSigReader.ann(cnode).get)
val csym = sig.evalT(0):ClassSymbol
override def superClass = Option(getNodeInfo(csym.jParent(sig)))
override def interfaces = csym.jInterfaces(sig).map(getNodeInfo)
}
def classInfo(name:String):ClassInfoSource = name match {
case null => null
case s => classNode(s) match {
case null => cl.findClass(s.replace('/', '.'))
case v if isScala(v) => new ScalaNodeInfoSource(v)
case v => v
}
}
}
import BaseNodeInfo._
class BaseNodeInfo(val clazz:ClassInfoSource)
{
def this(name:String) = this(classInfo(name))
val publicmethods = exportedMethods(clazz)//map of nameDesc to owner+method
def exportedMethods(o:ClassInfoSource):Map[String, MethodNodeInfo] =
{
val eMethods = o.methods.filter(!_.isPrivate)//defined accessible methods
.map(m => (m.name+m.desc, MethodNodeInfo(o.name, m))).toMap
eMethods ++ (o.superClass++o.interfaces).flatMap(_.publicmethods)
}
}
def finishBridgeCall(mv:MethodVisitor, mvdesc:String, opcode:Int, owner:String, name:String, desc:String)
{
val args = getArgumentTypes(mvdesc)
val ret = getReturnType(mvdesc)
var localIndex = 1
args.foreach{arg =>
mv.visitVarInsn(arg.getOpcode(ILOAD), localIndex)
localIndex+=width(arg)
}
mv.visitMethodInsn(opcode, owner, name, desc)
mv.visitInsn(ret.getOpcode(IRETURN))
mv.visitMaxs(Math.max(width(args)+1, width(ret)), width(args)+1)
}
def writeBridge(mv:MethodVisitor, mvdesc:String, opcode:Int, owner:String, name:String, desc:String)
{
mv.visitVarInsn(ALOAD, 0)
finishBridgeCall(mv, mvdesc, opcode, owner, name, desc)
}
def writeStaticBridge(mv:MethodNode, mname:String, t:MixinInfo) =
writeBridge(mv, mv.desc, INVOKESTATIC, t.tname, mname, staticDesc(t.name, mv.desc))
def mixinClasses(name:String, superClass:String, traits:Seq[String]) =
{
val traitInfos = traits.map(t => mixinMap(t.replace('.', '/')))
val cnode = new ClassNode()
cnode.visit(V1_6, ACC_PUBLIC, name, null, superClass.replace('.', '/'), traitInfos.map(_.name).toArray[String])
val minit = cnode.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null)
minit.visitVarInsn(ALOAD, 0)
minit.visitMethodInsn(INVOKESPECIAL, cnode.superName, "<init>", "()V")
val prevInfos = MList[MixinInfo]()
traitInfos.foreach{ t =>
minit.visitVarInsn(ALOAD, 0)
minit.visitMethodInsn(INVOKESTATIC, t.tname, "$init$", "(L"+t.name+";)V")
t.fields.foreach{ f =>
val fv = cnode.visitField(ACC_PRIVATE, f.accessName(t.name), f.desc, null, null).asInstanceOf[FieldNode]
val ftype = getType(fv.desc)
var mv = cnode.visitMethod(ACC_PUBLIC, fv.name, "()"+f.desc, null, null)
mv.visitVarInsn(ALOAD, 0)
mv.visitFieldInsn(GETFIELD, name, fv.name, fv.desc)
mv.visitInsn(ftype.getOpcode(IRETURN))
mv.visitMaxs(1, 1)
mv = cnode.visitMethod(ACC_PUBLIC, fv.name+"_$eq", "("+f.desc+")V", null, null)
mv.visitVarInsn(ALOAD, 0)
mv.visitVarInsn(ftype.getOpcode(ILOAD), 1)
mv.visitFieldInsn(PUTFIELD, name, fv.name, fv.desc)
mv.visitInsn(RETURN)
mv.visitMaxs(width(ftype)+1, width(ftype)+1)
}
t.supers.foreach{ s =>
val (name, desc) = seperateDesc(s._1)
val mv = cnode.visitMethod(ACC_PUBLIC, t.name.replace('/', '$')+"$$super$"+name, desc, null, null).asInstanceOf[MethodNode]
prevInfos.reverse.find(t => t.supers.contains(s._1)) match {//each super goes to the one before
case Some(st) => writeStaticBridge(mv, name, st)
case None => writeBridge(mv, desc, INVOKESPECIAL, s._2, name, desc)
}
}
prevInfos+=t
}
val methodSigs = MSet[String]()
traitInfos.reverse.foreach{ t => //last trait gets first pick on methods
t.methods.foreach{ m =>
if(!methodSigs(m.name+m.desc))
{
val mv = cnode.visitMethod(ACC_PUBLIC, m.name, m.desc, null, Array(m.exceptions:_*)).asInstanceOf[MethodNode]
copy(m, mv)
mv.instructions = new InsnList()
mv.tryCatchBlocks.clear()
writeStaticBridge(mv, m.name, t)
methodSigs+=m.name+m.desc
}
}
}
minit.visitInsn(RETURN)
minit.visitMaxs(1, 1)
define(cnode.name, createBytes(cnode, 0))
}
def seperateDesc(nameDesc:String) = {
val n = nameDesc.indexOf('(')
(nameDesc.substring(0, n), nameDesc.substring(n))
}
def staticDesc(owner:String, desc:String) =
{
val descT = getMethodType(desc)
getMethodDescriptor(descT.getReturnType, getType("L"+owner+";")+:descT.getArgumentTypes : _*)
}
def getSuper(minsn:MethodInsnNode, stack:StackAnalyser):Option[String] =
{
import StackAnalyser._
if(minsn.getOpcode != INVOKESPECIAL) return None
val oname = stack.owner.getInternalName
if(minsn.owner.equals(oname)) return None//private this
stack.peek(Type.getType(minsn.desc).getArgumentTypes.length) match {
case Load(This(o)) =>
case _ => return None//have to be invoked on this
}
getSuper(minsn.name+minsn.desc, oname)
}
def getSuper(nameDesc:String, cname:String) =
getNodeInfo(cname).clazz.superClass match {
case None => None//no super class
case Some(o) => o.publicmethods.get(nameDesc) match {
case None => None//no method with sig in super
case Some(m) => if(m.m.isAbstract)
None //abstract can't be called for supers
else
Some(m.owner)//congrats, that's a super
}
}
def registerJavaTrait(cnode:ClassNode)
{
if((cnode.access & ACC_INTERFACE) != 0)
throw new IllegalArgumentException("Cannot register java interface "+cnode.name+" as a multipart trait. Try register passThroughInterface")
if((cnode.access & ACC_ABSTRACT) != 0)
throw new IllegalArgumentException("Cannot register abstract class "+cnode.name+" as a multipart trait")
if(!cnode.innerClasses.isEmpty)
throw new IllegalArgumentException("Inner classes are not permitted for "+cnode.name+" as a multipart trait. Use scala")
val inode = new ClassNode()//impl node
inode.visit(V1_6, ACC_ABSTRACT|ACC_PUBLIC, cnode.name+"$class", null, "java/lang/Object", null)
inode.sourceFile = cnode.sourceFile
val fields = cnode.fields.map(f => (f.name, FieldMixin(f.name, f.desc, f.access))).toMap
val supers = MMap[String, String]()//nameDesc to super owner
val methods = MList[MethodNode]()
val methodSigs = cnode.methods.map(m => m.name+m.desc).toSet
val tnode = new ClassNode()//trait node (interface)
tnode.visit(V1_6, ACC_INTERFACE|ACC_ABSTRACT|ACC_PUBLIC, cnode.name, null, "java/lang/Object", Array(cnode.interfaces:_*))
def fname(name:String) = fields(name).accessName(cnode.name)
fields.values.foreach{ fnode =>
tnode.visitMethod(ACC_PUBLIC|ACC_ABSTRACT, fname(fnode.name), "()"+fnode.desc, null, null)
tnode.visitMethod(ACC_PUBLIC|ACC_ABSTRACT, fname(fnode.name)+"_$eq", "("+fnode.desc+")V", null, null)
}
def superInsn(minsn:MethodInsnNode) =
{
val bridgeName = cnode.name.replace('/', '$')+"$$super$"+minsn.name
if(!supers.contains(minsn.name+minsn.desc))
{
tnode.visitMethod(ACC_PUBLIC|ACC_ABSTRACT, bridgeName, minsn.desc, null, null)
supers.put(minsn.name+minsn.desc, minsn.owner)
}
new MethodInsnNode(INVOKEINTERFACE, cnode.name, bridgeName, minsn.desc)
}
def staticClone(mnode:MethodNode, name:String, access:Int) =
{
val mv = inode.visitMethod(access|ACC_STATIC, name,
staticDesc(cnode.name, mnode.desc),
null, Array(mnode.exceptions:_*)).asInstanceOf[MethodNode]
copy(mnode, mv)
mv
}
def staticTransform(mnode:MethodNode, base:MethodNode)
{
val stack = new StackAnalyser(getType(cnode.name), base)
val insnList = mnode.instructions
var insn = insnList.getFirst
def replace(newinsn:AbstractInsnNode)
{
insnList.insert(insn, newinsn)
insnList.remove(insn)
insn = newinsn
}
//transform
while(insn != null)
{
insn match {
case finsn:FieldInsnNode => insn.getOpcode match
{
case GETFIELD => replace(new MethodInsnNode(INVOKEINTERFACE, cnode.name,
fname(finsn.name), "()"+finsn.desc))
case PUTFIELD => replace(new MethodInsnNode(INVOKEINTERFACE, cnode.name,
fname(finsn.name)+"_$eq", "("+finsn.desc+")V"))
case _ =>
}
case minsn:MethodInsnNode => insn.getOpcode match
{
case INVOKESPECIAL =>
if(getSuper(minsn, stack).isDefined)
replace(superInsn(minsn))
case INVOKEVIRTUAL =>
if(minsn.owner.equals(cnode.name)) {
if(methodSigs.contains(minsn.name+minsn.desc))//call the interface method
minsn.setOpcode(INVOKEINTERFACE)
else {//cast to parent class and call
val mType = Type.getMethodType(minsn.desc)
val instanceEntry = stack.peek(width(mType.getArgumentTypes))
insnList.insert(instanceEntry.insn, new TypeInsnNode(CHECKCAST, cnode.superName))
minsn.owner = cnode.superName
}
}
case _ =>
}
case _ =>
}
stack.visitInsn(insn)
insn = insn.getNext
}
}
def convertMethod(mnode:MethodNode)
{
if(mnode.name.equals("<clinit>"))
throw new IllegalArgumentException("Static initialisers are not permitted "+cnode.name+" as a multipart trait")
if(mnode.name.equals("<init>"))
{
if(!mnode.desc.equals("()V"))
throw new IllegalArgumentException("Constructor arguments are not permitted "+cnode.name+" as a multipart trait")
val mv = staticClone(mnode, "$init$", ACC_PUBLIC)
def removeSuperConstructor()
{
def throwI = throw new IllegalArgumentException("Invalid constructor insn sequence "+cnode.name)
val insnList = mv.instructions
var insn = insnList.getFirst
var state = 0
while(insn != null && state < 2)
{
val next = insn.getNext
insn match {
case linsn:LabelNode =>
case linsn:LineNumberNode =>
case vinsn:VarInsnNode =>
if(state == 0)
{
if(vinsn.getOpcode == ALOAD && vinsn.`var` == 0)
{
state = 1
insnList.remove(insn)
}
else
throwI
}
else
throwI
case minsn:MethodInsnNode =>
if(state == 1)
{
if(minsn.getOpcode == INVOKESPECIAL && minsn.name.equals("<init>") &&
minsn.owner.equals(cnode.superName))
{
insnList.remove(insn)
state = 2
}
else
throwI
}
else
throwI
case _ => throwI
}
insn = next
}
if(state != 2)
throwI
}
removeSuperConstructor()
staticTransform(mv, mnode)
return
}
if((mnode.access & ACC_PRIVATE) == 0)
{
val mv = tnode.visitMethod(ACC_PUBLIC|ACC_ABSTRACT, mnode.name, mnode.desc, null, Array(mnode.exceptions:_*))
methods+=mv.asInstanceOf[MethodNode]
}
//convert that method!
val access = if((mnode.access & ACC_PRIVATE) == 0) ACC_PUBLIC else ACC_PRIVATE
val mv = staticClone(mnode, mnode.name, access)
staticTransform(mv, mnode)
}
cnode.methods.foreach(convertMethod)
define(inode.name, createBytes(inode, 0))
define(tnode.name, createBytes(tnode, 0))
mixinMap.put(tnode.name, MixinInfo(tnode.name, cnode.superName,
fields.values.toSeq,
methods.toSeq, supers.toMap))
}
def registerScalaTrait(cnode:ClassNode)
{
for(i <- cnode.interfaces)
{
val inode = classNode(i)
if(isScala(inode) && isTrait(inode))
throw new IllegalArgumentException("Multipart trait "+cnode.name+" cannot extend other traits")
}
val fieldAccessors = MMap[String, MethodSymbol]()
val fields = MList[MethodSymbol]()
val methods = MList[MethodNode]()
val supers = MMap[String, String]()
val sig = ScalaSigReader.read(ScalaSigReader.ann(cnode).get)
val csym:ClassSymbol = sig.evalT(0)
for(i <- 0 until sig.table.length)
{
import ScalaSignature._
val e = sig.table(i)
if(e.id == 8)//method
{
val sym:MethodSymbol = sig.evalT(i)
if(sym.isParam || !sym.owner.equals(csym)){}
else if(sym.isAccessor)
{
fieldAccessors.put(sym.name, sym)
}
else if(sym.isMethod)
{
val desc = sym.jDesc(sig)
if(sym.name.contains("super$"))
{
val name = sym.name.substring(6)
supers.put(name+desc, getSuper(name+desc, cnode.name).get)
}
else if(!sym.name.equals("$init$") && !sym.isPrivate)
{
methods+=findMethod(new ObfMapping(cnode.name, sym.name, desc), cnode)
}
}
else
{
fields+=sym
}
}
}
mixinMap.put(cnode.name, MixinInfo(cnode.name, csym.jParent(sig),
fields.map(sym => FieldMixin(sym.name.trim, getReturnType(sym.jDesc(sig)).getDescriptor,
if(fieldAccessors(sym.name.trim).isPrivate) ACC_PRIVATE else ACC_PUBLIC)),
methods, supers.toMap))
}
}

View file

@ -0,0 +1,311 @@
package codechicken.multipart.asm
import scala.collection.Seq
import codechicken.multipart.TileMultipart
import codechicken.multipart.TileMultipartClient
import scala.collection.JavaConversions._
import org.objectweb.asm.Opcodes._
import codechicken.lib.asm.CC_ClassWriter
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.FieldVisitor
import org.objectweb.asm.Label
import org.objectweb.asm.tree._
import codechicken.lib.asm.ASMHelper._
import codechicken.lib.asm.ObfMapping
import codechicken.multipart.MultipartGenerator
import ASMMixinCompiler._
object ASMMixinFactory extends IMultipartFactory
{
type TMClass = Class[_ <: TileMultipart]
private var ugenid = 0
private var generatorMap = Map[SuperSet, Constructor]()
def uniqueName(prefix:String):String = {
val ret = prefix+"$$"+ugenid
ugenid += 1
return ret
}
def simpleName(name:String) = name.substring(name.replace('/', '.').lastIndexOf('.')+1)
abstract class Constructor
{
def generate():TileMultipart
}
object SuperSet
{
val TileMultipartType = classOf[TileMultipart].getName
val TileMultipartClientType = classOf[TileMultipartClient].getName
def apply(types:Seq[String], client:Boolean) = new SuperSet(types, client)
}
class SuperSet(types:Seq[String], client:Boolean)
{
import SuperSet._
val set = baseType+:types.sorted
def baseType = if(client) TileMultipartClientType else TileMultipartType
override def equals(obj:Any) = obj match
{
case x:SuperSet => set == x.set
case _ => false
}
override def hashCode = set.hashCode()
def generate:TileMultipart = get.generate()
def get = generatorMap.getOrElse(this, gen_sync())
def gen_sync():Constructor = ASMMixinFactory.synchronized
{
return generatorMap.getOrElse(this, {
val gen = generator()
generatorMap = generatorMap+(this->gen)
gen
})
}
def generator():Constructor =
{
val startTime = System.currentTimeMillis
val cmpClass = if(!types.isEmpty)
mixinClasses(uniqueName("TileMultipart_cmp"), set.head, set.drop(1))
else
cl.loadClass(baseType)
MultipartGenerator.registerTileClass(cmpClass.asInstanceOf[TMClass], types.toSet)
val c = constructor(cmpClass)
DebugPrinter.log("Generation ["+types.mkString(", ")+"] took: "+(System.currentTimeMillis-startTime))
return c
}
}
def constructor(clazz:Class[_]):Constructor =
{
val name = uniqueName("TileMultipart_gen")
val cw = new CC_ClassWriter(ASM4)
val superName = classOf[Constructor].getName.replace('.', '/')
val className = clazz.getName.replace('.', '/')
var mv:MethodVisitor = null
cw.visit(V1_6, ACC_PUBLIC|ACC_SUPER, name, null, superName, null)
mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null)
mv.visitCode()
mv.visitVarInsn(ALOAD, 0)
mv.visitMethodInsn(INVOKESPECIAL, superName, "<init>", "()V")
mv.visitInsn(RETURN)
mv.visitMaxs(1, 1)
mv.visitEnd()
mv = cw.visitMethod(ACC_PUBLIC, "generate", "()Lcodechicken/multipart/TileMultipart;", null, null)
mv.visitCode()
mv.visitTypeInsn(NEW, className)
mv.visitInsn(DUP)
mv.visitMethodInsn(INVOKESPECIAL, className, "<init>", "()V")
mv.visitInsn(ARETURN)
mv.visitMaxs(2, 1)
mv.visitEnd()
cw.visitEnd()
return define(name, cw.toByteArray).newInstance.asInstanceOf[Constructor]
}
private def autoCompleteJavaTrait(cnode:ClassNode)
{
if(!cnode.fields.isEmpty && findMethod(new ObfMapping(cnode.name, "copyFrom", "(Lcodechicken/multipart/TileMultipart;)V"), cnode) == null)
{
val mv = cnode.visitMethod(ACC_PUBLIC, "copyFrom", "(Lcodechicken/multipart/TileMultipart;)V", null, null)
mv.visitVarInsn(ALOAD, 0)
mv.visitVarInsn(ALOAD, 1)
mv.visitMethodInsn(INVOKESPECIAL, "codechicken/multipart/TileMultipart", "copyFrom", "(Lcodechicken/multipart/TileMultipart;)V")
mv.visitVarInsn(ALOAD, 1)
mv.visitTypeInsn(INSTANCEOF, cnode.name)
val end = new Label()
mv.visitJumpInsn(IFEQ, end)
cnode.fields.foreach{ f =>
mv.visitVarInsn(ALOAD, 0)
mv.visitVarInsn(ALOAD, 1)
mv.visitFieldInsn(GETFIELD, cnode.name, f.name, f.desc)
mv.visitFieldInsn(PUTFIELD, cnode.name, f.name, f.desc)
}
mv.visitLabel(end)
mv.visitInsn(RETURN)
mv.visitMaxs(2, 2)
}
}
def registerTrait(s_interface:String, s_trait:String, client:Boolean)
{
val cnode = classNode(s_trait)
if(cnode == null)
throw new ClassNotFoundException(s_trait)
def superClass = getMixinInfo(cnode.name) match {
case Some(m) => m.parent
case None => BaseNodeInfo.getNodeInfo(cnode.name).clazz.superClass.get.clazz.name
}
superClass match {
case "codechicken/multipart/TileMultipartClient" => if(!client)
throw new IllegalArgumentException("Multipart trait "+s_trait+" cannot implement TileMultipartClient on the server")
case "codechicken/multipart/TileMultipart" =>
case _ => throw new IllegalArgumentException("Multipart trait "+s_trait+" must implement TileMultipart or TileMultipartClient")
}
if(getMixinInfo(cnode.name).isDefined)
return
if(isScala(cnode) && isTrait(cnode))
{
registerScalaTrait(cnode)
}
else
{
autoCompleteJavaTrait(cnode)
registerJavaTrait(cnode)
}
}
def generatePassThroughTrait(s_interface:String):String =
{
def passThroughTraitName(iName:String) =
"T" + (if(iName.startsWith("I")) iName.substring(1) else iName)
val tname = uniqueName(passThroughTraitName(simpleName(s_interface)))
val vname = "impl"
val iname = s_interface.replace('.', '/')
val idesc = "L"+iname+";"
val inode = classNode(s_interface)
if(inode == null) {
System.out.println("Unable to generate pass through trait for: "+s_interface+" class not found.")
return null
}
if((inode.access&ACC_INTERFACE) == 0) throw new IllegalArgumentException(s_interface+" is not an interface.")
val cw = new CC_ClassWriter(ASM4)
var mv:MethodVisitor = null
var fv:FieldVisitor = null
cw.visit(V1_6, ACC_PUBLIC|ACC_SUPER, tname, null, "codechicken/multipart/TileMultipart", Array(iname))
{
fv = cw.visitField(ACC_PRIVATE, vname, idesc, null, null)
fv.visitEnd()
}
{
mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null)
mv.visitCode()
mv.visitVarInsn(ALOAD, 0)
mv.visitMethodInsn(INVOKESPECIAL, "codechicken/multipart/TileMultipart", "<init>", "()V")
mv.visitInsn(RETURN)
mv.visitMaxs(1, 1)
mv.visitEnd()
}
{
mv = cw.visitMethod(ACC_PUBLIC, "bindPart", "(Lcodechicken/multipart/TMultiPart;)V", null, null)
mv.visitCode()
mv.visitVarInsn(ALOAD, 0)
mv.visitVarInsn(ALOAD, 1)
mv.visitMethodInsn(INVOKESPECIAL, "codechicken/multipart/TileMultipart", "bindPart", "(Lcodechicken/multipart/TMultiPart;)V")
mv.visitVarInsn(ALOAD, 1)
mv.visitTypeInsn(INSTANCEOF, iname)
val l2 = new Label()
mv.visitJumpInsn(IFEQ, l2)
val l3 = new Label()
mv.visitLabel(l3)
mv.visitVarInsn(ALOAD, 0)
mv.visitVarInsn(ALOAD, 1)
mv.visitTypeInsn(CHECKCAST, iname)
mv.visitFieldInsn(PUTFIELD, tname, vname, idesc)
mv.visitLabel(l2)
mv.visitFrame(F_SAME, 0, null, 0, null)
mv.visitInsn(RETURN)
mv.visitMaxs(2, 2)
mv.visitEnd()
}
{
mv = cw.visitMethod(ACC_PUBLIC, "partRemoved", "(Lcodechicken/multipart/TMultiPart;I)V", null, null)
mv.visitCode()
mv.visitVarInsn(ALOAD, 0)
mv.visitVarInsn(ALOAD, 1)
mv.visitVarInsn(ILOAD, 2)
mv.visitMethodInsn(INVOKESPECIAL, "codechicken/multipart/TileMultipart", "partRemoved", "(Lcodechicken/multipart/TMultiPart;I)V")
mv.visitVarInsn(ALOAD, 1)
mv.visitVarInsn(ALOAD, 0)
mv.visitFieldInsn(GETFIELD, tname, vname, idesc)
val l2 = new Label()
mv.visitJumpInsn(IF_ACMPNE, l2)
val l3 = new Label()
mv.visitLabel(l3)
mv.visitVarInsn(ALOAD, 0)
mv.visitInsn(ACONST_NULL)
mv.visitFieldInsn(PUTFIELD, tname, vname, idesc)
mv.visitLabel(l2)
mv.visitFrame(F_SAME, 0, null, 0, null)
mv.visitInsn(RETURN)
mv.visitMaxs(3, 3)
mv.visitEnd()
}
{
mv = cw.visitMethod(ACC_PUBLIC, "canAddPart", "(Lcodechicken/multipart/TMultiPart;)Z", null, null)
mv.visitCode()
mv.visitVarInsn(ALOAD, 0)
mv.visitFieldInsn(GETFIELD, tname, vname, idesc)
val l1 = new Label()
mv.visitJumpInsn(IFNULL, l1)
mv.visitVarInsn(ALOAD, 1)
mv.visitTypeInsn(INSTANCEOF, iname)
mv.visitJumpInsn(IFEQ, l1)
val l2 = new Label()
mv.visitLabel(l2)
mv.visitInsn(ICONST_0)
mv.visitInsn(IRETURN)
mv.visitLabel(l1)
mv.visitFrame(F_SAME, 0, null, 0, null)
mv.visitVarInsn(ALOAD, 0)
mv.visitVarInsn(ALOAD, 1)
mv.visitMethodInsn(INVOKESPECIAL, "codechicken/multipart/TileMultipart", "canAddPart", "(Lcodechicken/multipart/TMultiPart;)Z")
mv.visitInsn(IRETURN)
mv.visitMaxs(2, 2)
mv.visitEnd()
}
def methods(cnode:ClassNode):Map[String, MethodNode] =
{
val m = cnode.methods.map(m => (m.name+m.desc, m)).toMap
if(cnode.interfaces != null)
m++cnode.interfaces.flatMap(i => methods(classNode(i)))
else
m
}
def generatePassThroughMethod(m:MethodNode)
{
mv = cw.visitMethod(ACC_PUBLIC, m.name, m.desc, m.signature, Array(m.exceptions:_*))
mv.visitVarInsn(ALOAD, 0)
mv.visitFieldInsn(GETFIELD, tname, vname, idesc)
finishBridgeCall(mv, m.desc, INVOKEINTERFACE, iname, m.name, m.desc)
}
methods(inode).values.foreach(generatePassThroughMethod)
cw.visitEnd()
internalDefine(tname, cw.toByteArray)
registerTrait(s_interface, tname, false)
return tname
}
def generateTile(types:Seq[String], client:Boolean) = SuperSet(types, client).generate
}

View file

@ -0,0 +1,213 @@
/* __ *\
** ________ ___ / / ___ Scala API **
** / __/ __// _ | / / / _ | (c) 2007-2013, LAMP/EPFL **
** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ **
** /____/\___/_/ |_/____/_/ | | **
** |/ **
\* */
package codechicken.multipart.asm
object ByteCodecs {
def avoidZero(src: Array[Byte]): Array[Byte] = {
var i = 0
val srclen = src.length
var count = 0
while (i < srclen) {
if (src(i) == 0x7f) count += 1
i += 1
}
val dst = new Array[Byte](srclen + count)
i = 0
var j = 0
while (i < srclen) {
val in = src(i)
if (in == 0x7f) {
dst(j) = (0xc0).toByte
dst(j + 1) = (0x80).toByte
j += 2
} else {
dst(j) = (in + 1).toByte
j += 1
}
i += 1
}
dst
}
def regenerateZero(src: Array[Byte]): Int = {
var i = 0
val srclen = src.length
var j = 0
while (i < srclen) {
val in: Int = src(i) & 0xff
if (in == 0xc0 && (src(i + 1) & 0xff) == 0x80) {
src(j) = 0x7f
i += 2
} else if (in == 0) {
src(j) = 0x7f
i += 1
} else {
src(j) = (in - 1).toByte
i += 1
}
j += 1
}
j
}
def encode8to7(src: Array[Byte]): Array[Byte] = {
val srclen = src.length
val dstlen = (srclen * 8 + 6) / 7
val dst = new Array[Byte](dstlen)
var i = 0
var j = 0
while (i + 6 < srclen) {
var in: Int = src(i) & 0xff
dst(j) = (in & 0x7f).toByte
var out: Int = in >>> 7
in = src(i + 1) & 0xff
dst(j + 1) = (out | (in << 1) & 0x7f).toByte
out = in >>> 6
in = src(i + 2) & 0xff
dst(j + 2) = (out | (in << 2) & 0x7f).toByte
out = in >>> 5
in = src(i + 3) & 0xff
dst(j + 3) = (out | (in << 3) & 0x7f).toByte
out = in >>> 4
in = src(i + 4) & 0xff
dst(j + 4) = (out | (in << 4) & 0x7f).toByte
out = in >>> 3
in = src(i + 5) & 0xff
dst(j + 5) = (out | (in << 5) & 0x7f).toByte
out = in >>> 2
in = src(i + 6) & 0xff
dst(j + 6) = (out | (in << 6) & 0x7f).toByte
out = in >>> 1
dst(j + 7) = out.toByte
i += 7
j += 8
}
if (i < srclen) {
var in: Int = src(i) & 0xff
dst(j) = (in & 0x7f).toByte; j += 1
var out: Int = in >>> 7
if (i + 1 < srclen) {
in = src(i + 1) & 0xff
dst(j) = (out | (in << 1) & 0x7f).toByte; j += 1
out = in >>> 6
if (i + 2 < srclen) {
in = src(i + 2) & 0xff
dst(j) = (out | (in << 2) & 0x7f).toByte; j += 1
out = in >>> 5
if (i + 3 < srclen) {
in = src(i + 3) & 0xff
dst(j) = (out | (in << 3) & 0x7f).toByte; j += 1
out = in >>> 4
if (i + 4 < srclen) {
in = src(i + 4) & 0xff
dst(j) = (out | (in << 4) & 0x7f).toByte; j += 1
out = in >>> 3
if (i + 5 < srclen) {
in = src(i + 5) & 0xff
dst(j) = (out | (in << 5) & 0x7f).toByte; j += 1
out = in >>> 2
}
}
}
}
}
if (j < dstlen) dst(j) = out.toByte
}
dst
}
def decode7to8(src: Array[Byte], srclen: Int): Int = {
var i = 0
var j = 0
val dstlen = (srclen * 7 + 7) / 8
while (i + 7 < srclen) {
var out: Int = src(i).toInt
var in: Byte = src(i + 1)
src(j) = (out | (in & 0x01) << 7).toByte
out = in >>> 1
in = src(i + 2)
src(j + 1) = (out | (in & 0x03) << 6).toByte
out = in >>> 2
in = src(i + 3)
src(j + 2) = (out | (in & 0x07) << 5).toByte
out = in >>> 3
in = src(i + 4)
src(j + 3) = (out | (in & 0x0f) << 4).toByte
out = in >>> 4
in = src(i + 5)
src(j + 4) = (out | (in & 0x1f) << 3).toByte
out = in >>> 5
in = src(i + 6)
src(j + 5) = (out | (in & 0x3f) << 2).toByte
out = in >>> 6
in = src(i + 7)
src(j + 6) = (out | in << 1).toByte
i += 8
j += 7
}
if (i < srclen) {
var out: Int = src(i).toInt
if (i + 1 < srclen) {
var in: Byte = src(i + 1)
src(j) = (out | (in & 0x01) << 7).toByte; j += 1
out = in >>> 1
if (i + 2 < srclen) {
in = src(i + 2)
src(j) = (out | (in & 0x03) << 6).toByte; j += 1
out = in >>> 2
if (i + 3 < srclen) {
in = src(i + 3)
src(j) = (out | (in & 0x07) << 5).toByte; j += 1
out = in >>> 3
if (i + 4 < srclen) {
in = src(i + 4)
src(j) = (out | (in & 0x0f) << 4).toByte; j += 1
out = in >>> 4
if (i + 5 < srclen) {
in = src(i + 5)
src(j) = (out | (in & 0x1f) << 3).toByte; j += 1
out = in >>> 5
if (i + 6 < srclen) {
in = src(i + 6)
src(j) = (out | (in & 0x3f) << 2).toByte; j += 1
out = in >>> 6
}
}
}
}
}
}
if (j < dstlen) src(j) = out.toByte
}
dstlen
}
def encode(xs: Array[Byte]): Array[Byte] = avoidZero(encode8to7(xs))
/**
* Destructively decodes array xs and returns the length of the decoded array.
*
* Sometimes returns (length+1) of the decoded array. Example:
*
* scala> val enc = scala.reflect.generic.ByteCodecs.encode(Array(1,2,3))
* enc: Array[Byte] = Array(2, 5, 13, 1)
*
* scala> scala.reflect.generic.ByteCodecs.decode(enc)
* res43: Int = 4
*
* scala> enc
* res44: Array[Byte] = Array(1, 2, 3, 0)
*
* However, this does not always happen.
*/
def decode(xs: Array[Byte]): Int = {
val len = regenerateZero(xs)
decode7to8(xs, len)
}
}

View file

@ -0,0 +1,12 @@
package codechicken.multipart.asm
import codechicken.multipart.TileMultipart
trait IMultipartFactory
{
def registerTrait(s_interface:String, s_trait:String, client:Boolean)
def generatePassThroughTrait(s_interface:String):String
def generateTile(types:Seq[String], client:Boolean):TileMultipart
}

View file

@ -0,0 +1,341 @@
package codechicken.multipart.asm
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.AnnotationNode
import org.objectweb.asm.tree.FieldNode
import org.objectweb.asm.tree.MethodNode
import java.util.{List => JList}
import scala.collection.mutable.{ListBuffer => MList}
import scala.collection.JavaConverters._
import scala.collection.JavaConversions._
import ScalaSignature._
object ScalaSignature
{
object Bytes
{
implicit def reader(bc:Bytes) = new ByteCodeReader(bc)
def apply(bytes:Array[Byte], pos:Int, len:Int) = new Bytes(bytes, pos, len)
def apply(bytes:Array[Byte]):Bytes = apply(bytes, 0, bytes.length)
}
class Bytes(val bytes:Array[Byte], val pos:Int, val len:Int)
{
def section() = bytes take pos drop len
}
trait Flags
{
def hasFlag(flag:Int):Boolean
def isPrivate = hasFlag(0x00000004)
def isProtected = hasFlag(0x00000008)
def isAbstract = hasFlag(0x00000080)
def isInterface = hasFlag(0x00000800)
def isMethod = hasFlag(0x00000200)
def isParam = hasFlag(0x00002000)
def isStatic = hasFlag(0x00800000)
def isTrait = hasFlag(0x02000000)
def isAccessor = hasFlag(0x08000000)
}
trait SymbolRef extends Flags
{
def full:String
def flags:Int
def hasFlag(flag:Int) = (flags&flag) != 0
}
case class ClassSymbol(name:String, owner:SymbolRef, flags:Int, info:Int) extends SymbolRef
{
override def toString = "ClassSymbol("+name+","+owner+","+flags.toHexString+","+info+")"
def full = owner.full+"."+name
def info(sig:ScalaSignature):ClassType = sig.evalT(info)
def jParent(sig:ScalaSignature) = info(sig).parent.jName
def jInterfaces(sig:ScalaSignature) = info(sig).interfaces.map(_.jName)
}
case class MethodSymbol(name:String, owner:SymbolRef, flags:Int, info:Int) extends SymbolRef
{
override def toString = "MethodSymbol("+name+","+owner+","+flags.toHexString+","+info+")"
def full = owner.full+"."+name
def info(sig:ScalaSignature):SMethodType = sig.evalT(info)
def jDesc(sig:ScalaSignature):String = info(sig).jDesc(sig)
}
case class ExternalSymbol(name:String) extends SymbolRef
{
override def toString = name
def full = name
def flags = 0
}
case object NoSymbol extends SymbolRef
{
def full = "<no symbol>"
def flags = 0
}
trait SMethodType
{
def jDesc(sig:ScalaSignature):String = "("+params.map(m => m.info(sig).returnType.jDesc).mkString+")"+returnType.jDesc
def returnType:TypeRef
def params:List[MethodSymbol]
}
case class ClassType(owner:SymbolRef, parents:List[TypeRef])
{
def parent = parents.head
def interfaces = parents.drop(1)
}
case class MethodType(returnType:TypeRef, params:List[MethodSymbol]) extends SMethodType
case class ParameterlessType(returnType:TypeRef) extends SMethodType
{
def params = List()
}
trait TypeRef
{
def name:String
def jName = name.replace('.', '/') match {
case "scala/AnyRef" => "java/lang/Object"
case s => s
}
def jDesc:String = name match
{
case "scala.Array" => null
case "scala.Long" => "J"
case "scala.Int" => "I"
case "scala.Short" => "S"
case "scala.Byte" => "B"
case "scala.Double" => "D"
case "scala.Float" => "F"
case "scala.Boolean" => "Z"
case "scala.Unit" => "V"
case _ => "L"+jName+";"
}
}
case class TypeRefType(owner:TypeRef, sym:SymbolRef, typArgs:List[TypeRef]) extends TypeRef with SMethodType
{
def params = List()
def returnType = this
def name = sym.full
override def jDesc = name match
{
case "scala.Array" => "["+typArgs(0).jDesc
case _ => super.jDesc
}
}
case class ThisType(sym:SymbolRef) extends TypeRef
{
def name = sym.full
}
case class SingleType(owner:TypeRef, sym:SymbolRef) extends TypeRef
{
def name = sym.full
}
case object NoType extends TypeRef
{
def name = "<no type>"
}
class SigEntry(val start:Int, val bytes:Bytes)
{
def id = bytes.bytes(start)
def delete()
{
bytes.bytes(start) = 3
}
}
}
case class ScalaSignature(major:Int, minor:Int, table:Array[SigEntry], bytes:Bytes)
{
def evalS(i:Int):String = {
val e = table(i)
val bc = e.bytes
val bcr = bc:ByteCodeReader
return e.id match
{
case 1|2 => bcr.readString(bc.len)
case 3 => NoSymbol.full
case 9|10 =>
var s = evalS(bcr.readNat)
if(bc.pos+bc.len > bcr.pos)
s = evalS(bcr.readNat)+"."+s
s
}
}
def evalT[T](i:Int):T = eval(i).asInstanceOf[T]
def evalList[T](bcr:ByteCodeReader) =
{
var l = MList[T]()
while(bcr.more)
l+=evalT(bcr.readNat)
l.toList
}
def eval(i:Int):Any = {//we only parse the ones we actually care about
val e = table(i)
val bc = e.bytes
val bcr = bc:ByteCodeReader
return e.id match
{
case 1|2 => evalS(i)
case 6 => ClassSymbol(evalS(bcr.readNat), evalT(bcr.readNat), bcr.readNat, bcr.readNat)
case 8 => MethodSymbol(evalS(bcr.readNat), evalT(bcr.readNat), bcr.readNat, bcr.readNat)
case 9|10 => ExternalSymbol(evalS(i))
case 11|12 => NoType //12 is actually NoPrefixType (no lower bound)
case 13 => ThisType(evalT(bcr.readNat))
case 14 => SingleType(evalT(bcr.readNat), evalT(bcr.readNat))
case 16 => TypeRefType(evalT(bcr.readNat), evalT(bcr.readNat), evalList(bcr))
case 19 => ClassType(evalT(bcr.readNat), evalList(bcr))
case 20 => MethodType(evalT(bcr.readNat), evalList(bcr))
case 21|48 => ParameterlessType(evalT(bcr.readNat))//48 is actually a bounded super type, but it should work fine for our purposes
case _ => NoSymbol
}
}
}
class ByteCodeReader(val bc:Bytes)
{
var pos = bc.pos
def more = pos < bc.pos+bc.len
def readString(len:Int) = advance(len)(new String(bc.bytes drop pos take len))
def readByte = advance(1)(bc.bytes(pos))
def readNat:Int =
{
var r = 0
var b = 0
do
{
b = readByte
r = r<<7|b&0x7F
}
while((b&0x80) != 0)
return r
}
def advance[A](len:Int)(r:A):A =
{
if(pos+len > bc.pos+bc.len)
throw new IllegalArgumentException("Ran off the end of bytecode")
pos+=len
return r
}
def readEntry =
{
val p = pos
val tpe:Int = readByte
val len = readNat
advance(len)(new SigEntry(p, new Bytes(bc.bytes, pos, len)))
}
def readSig =
{
val major = readByte
val minor = readByte
val table = new Array[SigEntry](readNat)
for(i <- 0 until table.size)
table(i) = readEntry
ScalaSignature(major, minor, table, bc)
}
}
object ScalaSigReader
{
def decode(s:String):Array[Byte] =
{
val bytes = s.getBytes
return bytes take ByteCodecs.decode(bytes)
}
def encode(b:Array[Byte]):String =
{
val bytes = ByteCodecs.encode8to7(b)
var i = 0
while(i < bytes.length)
{
bytes(i) = ((bytes(i)+1)&0x7F).toByte
i+=1
}
return new String(bytes.take(bytes.length-1), "UTF-8")
}
def read(ann:AnnotationNode):ScalaSignature = Bytes(decode(ann.values.get(1).asInstanceOf[String])).readSig
def write(sig:ScalaSignature, ann:AnnotationNode) = ann.values.set(1, encode(sig.bytes.bytes))
def ann(cnode:ClassNode):Option[AnnotationNode] = cnode.visibleAnnotations match {
case null => None
case a => a.find(ann => ann.desc.equals("Lscala/reflect/ScalaSignature;"))
}
}
class ScalaSigSideTransformer
{
def transform(ann:AnnotationNode, cnode:ClassNode, removedFields:JList[FieldNode], removedMethods:JList[MethodNode])
{
if(removedFields.isEmpty && removedMethods.isEmpty)
return
val remFields = removedFields.asScala.map(f => (f.name, f.desc.replace('$', '/')))
val remMethods = removedMethods.asScala.map(f => (f.name, f.desc.replace('$', '/')))
val sig = ScalaSigReader.read(ann)
for(i <- 0 until sig.table.length)
{
val e = sig.table(i)
if(e.id == 8)//check and remove
{
val sym:MethodSymbol = sig.evalT(i)
if(sym.isAccessor)
{
val fName = if(sym.name.endsWith("_$eq")) sym.name.substring(0, sym.name.length-4) else sym.name
if(remFields.find(t => t._1 == sym.name.trim).nonEmpty)
{
e.delete()
val it = cnode.methods.iterator
while(it.hasNext)
{
val m = it.next
if(m.name == sym.name && m.desc == sym.jDesc(sig))
it.remove()
}
}
}
else if(sym.isMethod)
{
if(remMethods.find(t => t._1 == sym.name && t._2 == sym.jDesc(sig)).nonEmpty)
e.delete()
}
else//field
{
if(remFields.find(t => t._1 == sym.name.trim).nonEmpty)
e.delete()
}
}
}
ScalaSigReader.write(sig, ann)
}
}

View file

@ -0,0 +1,314 @@
package codechicken.multipart.asm
import scala.collection.mutable.{Map => MMap, ListBuffer => MList}
import org.objectweb.asm.tree._
import org.objectweb.asm.Opcodes._
import org.objectweb.asm.Type
import org.objectweb.asm.Type._
import scala.collection.JavaConversions._
object StackAnalyser
{
def width(t:Type):Int = t.getSize
def width(s:String):Int = width(Type.getType(s))
def width(it:Iterable[Type]):Int = it.foldLeft(0)(_+width(_))
abstract class StackEntry(implicit val insn:AbstractInsnNode)
{
def getType:Type
}
abstract class LocalEntry
{
def getType:Type
}
case class This(owner:Type) extends LocalEntry
{
def getType = owner
}
case class Param(i:Int, t:Type) extends LocalEntry
{
def getType = t
}
case class Store(e:StackEntry)(implicit val insn:AbstractInsnNode) extends LocalEntry
{
def getType = e.getType
}
case class Const(c:Any)(implicit insn:AbstractInsnNode) extends StackEntry
{
def getType = c match {
case o:Byte => BYTE_TYPE
case o:Short => SHORT_TYPE
case o:Int => INT_TYPE
case o:Long => LONG_TYPE
case o:Float => FLOAT_TYPE
case o:Double => DOUBLE_TYPE
case o:Char => CHAR_TYPE
case o:Boolean => BOOLEAN_TYPE
case o:String => getObjectType("java/lang/String")
case null => getObjectType("java/lang/Object")
case _ => throw new IllegalArgumentException("Unknown const "+c)
}
}
case class Load(e:LocalEntry)(implicit insn:AbstractInsnNode) extends StackEntry
{
def getType = e.getType
}
case class UnaryOp(op:Int, e:StackEntry)(implicit insn:AbstractInsnNode) extends StackEntry
{
def getType = e.getType
}
case class BinaryOp(op:Int, e2:StackEntry, e1:StackEntry)(implicit insn:AbstractInsnNode) extends StackEntry
{
def getType = e1.getType
}
case class PrimitiveCast(e:StackEntry, t:Type)(implicit insn:AbstractInsnNode) extends StackEntry
{
def getType = t
}
case class ReturnAddress()(implicit insn:AbstractInsnNode) extends StackEntry
{
def getType = INT_TYPE
}
case class GetField(obj:StackEntry, field:FieldInsnNode)(implicit insn:AbstractInsnNode) extends StackEntry
{
def getType = Type.getType(field.desc)
}
case class Invoke(op:Int, params:Array[StackEntry], obj:StackEntry, method:MethodInsnNode)(implicit insn:AbstractInsnNode) extends StackEntry
{
def getType = Type.getMethodType(method.desc).getReturnType
}
case class New(t:Type)(implicit insn:AbstractInsnNode) extends StackEntry
{
def getType = t
}
case class NewArray(len:StackEntry, t:Type)(implicit insn:AbstractInsnNode) extends StackEntry
{
def getType = t
}
case class ArrayLength(array:StackEntry)(implicit insn:AbstractInsnNode) extends StackEntry
{
def getType = INT_TYPE
}
case class ArrayLoad(index:StackEntry, e:StackEntry)(implicit insn:AbstractInsnNode) extends StackEntry
{
def getType = e.getType.getElementType
}
case class Cast(obj:StackEntry, t:Type)(implicit insn:AbstractInsnNode) extends StackEntry
{
def getType = t
}
case class NewMultiArray(sizes:Array[StackEntry], t:Type)(implicit insn:AbstractInsnNode) extends StackEntry
{
def getType = t
}
case class CaughtException(t:Type)(implicit insn:AbstractInsnNode) extends StackEntry
{
def getType = t
}
}
class StackAnalyser(val owner:Type, val m:MethodNode)
{
import StackAnalyser._
val stack = MList[StackEntry]()
val locals = MList[LocalEntry]()
private val catchHandlers = MMap[LabelNode, TryCatchBlockNode]()
{
if((m.access & ACC_STATIC) == 0)
pushL(This(owner))
val ptypes = getArgumentTypes(m.desc)
for(i <- 0 until ptypes.length)
pushL(Param(i, ptypes(i)))
m.tryCatchBlocks.foreach(b => catchHandlers.put(b.handler, b))
}
def pushL(entry:LocalEntry) = setL(locals.size, entry)
def setL(i:Int, entry:LocalEntry) =
{
while(i+entry.getType.getSize > locals.size) locals += null
locals(i) = entry
if(entry.getType.getSize == 2)
locals(i+1) = entry
}
def push(entry:StackEntry) = insert(0, entry)
def _pop(i:Int = 0) = stack.remove(stack.size-i-1)
def pop(i:Int = 0) =
{
val e = _pop(i)
if(e.getType.getSize == 2)
{
if(peek(i) != e) throw new IllegalStateException("Wide stack entry elems don't match ("+e+","+peek(i))
_pop(i)
}
e
}
def peek(i:Int = 0) = stack(stack.size-i-1)
def insert(i:Int, entry:StackEntry)
{
if(entry.getType.getSize == 0)
return
stack.insert(stack.size-i, entry)
if(entry.getType.getSize == 2)
stack.insert(stack.size-i, entry)
}
def popArgs(desc:String) =
{
val t = getType(desc)
val args = new Array[StackEntry](t.getArgumentTypes.length)
for(i <- 0 until args.length)
args(args.length-i-1) = pop()
args
}
def visitInsn(ainsn:AbstractInsnNode)
{
implicit val thisInsn = ainsn//passes to any StackEntry we create
ainsn match {
case insn:InsnNode => ainsn.getOpcode match
{
case ACONST_NULL => push(Const(null))
case ICONST_M1 => push(Const(-1))
case ICONST_0 => push(Const(0))
case ICONST_1 => push(Const(1))
case ICONST_2 => push(Const(2))
case ICONST_3 => push(Const(3))
case ICONST_4 => push(Const(4))
case ICONST_5 => push(Const(5))
case LCONST_0 => push(Const(0L))
case LCONST_1 => push(Const(1L))
case FCONST_0 => push(Const(0F))
case FCONST_1 => push(Const(1F))
case FCONST_2 => push(Const(2F))
case DCONST_0 => push(Const(0D))
case DCONST_1 => push(Const(1D))
case i if i >= IALOAD && i <= SALOAD =>
push(ArrayLoad(pop(), pop()))
case i if i >= IASTORE && i <= SASTORE =>
pop(); pop(); pop()
case POP => _pop()
case POP2 => _pop(); _pop()
case DUP => push(peek())
case DUP_X1 => insert(2, peek())
case DUP_X2 => insert(3, peek())
case DUP2 => push(peek(1)); push(peek(1))
case DUP2_X1 => insert(3, peek(1)); insert(3, peek())
case DUP2_X2 => insert(4, peek(1)); insert(4, peek())
case SWAP => push(pop(1))
case i if i >= IADD && i <= DREM =>
push(BinaryOp(i, pop(), pop()))
case i if i >= INEG && i <= DNEG =>
push(UnaryOp(i, pop()))
case i if i >= ISHL && i <= LXOR =>
push(BinaryOp(i, pop(), pop()))
case L2I|F2I|D2I => push(PrimitiveCast(pop(), DOUBLE_TYPE))
case I2L|F2L|D2L => push(PrimitiveCast(pop(), LONG_TYPE))
case I2F|L2F|D2F => push(PrimitiveCast(pop(), FLOAT_TYPE))
case I2D|L2D|F2D => push(PrimitiveCast(pop(), DOUBLE_TYPE))
case I2B => push(PrimitiveCast(pop(), BYTE_TYPE))
case I2C => push(PrimitiveCast(pop(), CHAR_TYPE))
case I2S => push(PrimitiveCast(pop(), SHORT_TYPE))
case i if i >= LCMP && i <= DCMPG =>
push(BinaryOp(i, pop(), pop()))
case i if i >= IRETURN && i <= ARETURN => pop()
case ARRAYLENGTH => push(ArrayLength(pop()))
case ATHROW => pop()
case MONITORENTER|MONITOREXIT => pop()
case _ =>
}
case iinsn:IntInsnNode => ainsn.getOpcode match
{
case BIPUSH => push(Const(iinsn.operand.toByte))
case SIPUSH => push(Const(iinsn.operand.toShort))
}
case ldcinsn:LdcInsnNode => ainsn.getOpcode match
{
case LDC => push(Const(ldcinsn.cst))
}
case vinsn:VarInsnNode => ainsn.getOpcode match
{
case i if i >= ILOAD && i <= ALOAD =>
push(Load(locals(vinsn.`var`)))
case i if i >= ISTORE && i <= ASTORE =>
setL(vinsn.`var`, Store(pop()))
}
case incinsn:IincInsnNode => ainsn.getOpcode match
{
case IINC => setL(incinsn.`var`, Store(BinaryOp(IINC, Const(incinsn.incr), Load(locals(incinsn.`var`)))))
}
case jinsn:JumpInsnNode => ainsn.getOpcode match
{
case i if i >= IFEQ && i <= IFLE => pop()
case i if i >= IF_ICMPEQ && i <= IF_ACMPNE => pop(); pop()
case JSR => push(ReturnAddress())
case IFNULL|IFNONNULL => pop()
case GOTO =>
}
case sinsn:TableSwitchInsnNode => pop()
case linsn:LookupSwitchInsnNode => pop()
case finsn:FieldInsnNode => ainsn.getOpcode match
{
case GETSTATIC => push(GetField(null, finsn))
case PUTSTATIC => pop()
case GETFIELD => push(GetField(pop(), finsn))
case PUTFIELD => pop(); pop()
}
case minsn:MethodInsnNode => ainsn.getOpcode match
{
case INVOKEVIRTUAL|INVOKESPECIAL|INVOKEINTERFACE =>
push(Invoke(ainsn.getOpcode, popArgs(minsn.desc), pop(), minsn))
case INVOKESTATIC =>
push(Invoke(ainsn.getOpcode, popArgs(minsn.desc), null, minsn))
}
case tinsn:TypeInsnNode => ainsn.getOpcode match
{
case NEW => push(New(getType(tinsn.desc)))
case NEWARRAY => push(NewArray(pop(), getType(tinsn.desc)))
case ANEWARRAY => push(NewArray(pop(), getType("["+tinsn.desc)))
case CHECKCAST => push(Cast(pop(), getType(tinsn.desc)))
case INSTANCEOF => push(UnaryOp(INSTANCEOF, pop()))
}
case mainsn:MultiANewArrayInsnNode =>
val sizes = new Array[StackEntry](mainsn.dims)
for(i <- 0 until sizes.length)
sizes(i) = pop()
push(NewMultiArray(sizes, getType(mainsn.desc)))
/*case fnode:FrameNode => fnode.`type` match
{
case F_NEW|F_FULL => println("reset stacks/locals")
case F_APPEND => println("add local")
case F_CHOP => println("rem locals")
case F_SAME => println("reset")
case F_SAME1 => println("reset locals and all but bottom stack")
}*/
case lnode:LabelNode =>
catchHandlers.get(lnode) match
{
case Some(tcblock) => push(CaughtException(Type.getType(tcblock.`type`)))
case None =>
}
case _ =>
}
}
}

View file

@ -0,0 +1,87 @@
package codechicken.multipart.handler
import codechicken.multipart.TileMultipart
import net.minecraftforge.event.ForgeSubscribe
import cpw.mods.fml.common.network.IConnectionHandler
import net.minecraft.network.packet.NetHandler
import net.minecraft.network.INetworkManager
import net.minecraft.network.packet.Packet1Login
import cpw.mods.fml.common.network.Player
import net.minecraft.network.NetLoginHandler
import net.minecraft.server.MinecraftServer
import codechicken.lib.packet.PacketCustom
import codechicken.multipart.MultiPartRegistry
import net.minecraftforge.event.world._
import cpw.mods.fml.common.ITickHandler
import java.util.EnumSet
import cpw.mods.fml.common.TickType
import scala.collection.JavaConverters._
import java.util.List
import net.minecraft.entity.player.EntityPlayerMP
import net.minecraftforge.event.EventPriority
import cpw.mods.fml.relauncher.SideOnly
import cpw.mods.fml.relauncher.Side
import net.minecraftforge.client.event.DrawBlockHighlightEvent
import net.minecraft.util.EnumMovingObjectType
import codechicken.multipart.BlockMultipart
object MultipartEventHandler extends IConnectionHandler with ITickHandler
{
@ForgeSubscribe(priority = EventPriority.HIGHEST)
def tileEntityLoad(event:ChunkDataEvent.Load)
{
MultipartSaveLoad.loadTiles(event.getChunk)
}
@ForgeSubscribe
def worldUnLoad(event:WorldEvent.Unload)
{
MultipartSPH.onWorldUnload(event.world)
}
@ForgeSubscribe
def chunkWatch(event:ChunkWatchEvent.Watch)
{
val cc = event.chunk
MultipartSPH.onChunkWatch(event.player, event.player.worldObj.getChunkFromChunkCoords(cc.chunkXPos, cc.chunkZPos))
}
@ForgeSubscribe
@SideOnly(Side.CLIENT)
def drawBlockHighlight(event:DrawBlockHighlightEvent)
{
if(event.target != null && event.target.typeOfHit == EnumMovingObjectType.TILE &&
event.player.worldObj.getBlockTileEntity(event.target.blockX, event.target.blockY, event.target.blockZ).isInstanceOf[TileMultipart])
{
if(BlockMultipart.drawHighlight(event.player.worldObj, event.player, event.target, event.partialTicks))
event.setCanceled(true)
}
}
def connectionReceived(loginHandler:NetLoginHandler, netManager:INetworkManager):String =
{
val packet = new PacketCustom(MultipartSPH.registryChannel, 1)
MultiPartRegistry.writeIDMap(packet)
netManager.addToSendQueue(packet.toPacket)
return null
}
def clientLoggedIn(netHandler:NetHandler, netManager:INetworkManager, packet:Packet1Login){}
def playerLoggedIn(player:Player, netHandler:NetHandler, netManager:INetworkManager){}
def connectionOpened(netHandler:NetHandler, server:String, port:Int, netManager:INetworkManager){}
def connectionOpened(netHandler:NetHandler, server:MinecraftServer, netManager:INetworkManager){}
def connectionClosed(netManager:INetworkManager){}
def ticks = EnumSet.of(TickType.SERVER)
def getLabel = "Multipart"
def tickStart(tickType:EnumSet[TickType], data:Object*){}
def tickEnd(tickType:EnumSet[TickType], data:Object*)
{
if(tickType.contains(TickType.SERVER))
{
MultipartSPH.onTickEnd(
MinecraftServer.getServer.getConfigurationManager.playerEntityList
.asInstanceOf[List[EntityPlayerMP]].asScala)
}
}
}

View file

@ -0,0 +1,45 @@
package codechicken.multipart.handler
import cpw.mods.fml.common.network.NetworkMod
import cpw.mods.fml.common.Mod
import cpw.mods.fml.common.event.FMLPostInitializationEvent
import cpw.mods.fml.common.event.FMLPreInitializationEvent
import cpw.mods.fml.common.event.FMLInitializationEvent
import cpw.mods.fml.common.Mod.EventHandler
import cpw.mods.fml.common.event.FMLServerAboutToStartEvent
import codechicken.multipart.MultiPartRegistry
import codechicken.lib.packet.PacketCustom.CustomTinyPacketHandler
@Mod(modid = "ForgeMultipart", acceptedMinecraftVersions = "[1.6.4]",
modLanguage="scala")
@NetworkMod(clientSideRequired = true, serverSideRequired = true, tinyPacketHandler=classOf[CustomTinyPacketHandler])
object MultipartMod
{
@EventHandler
def preInit(event:FMLPreInitializationEvent)
{
MultipartProxy.preInit(event.getModConfigurationDirectory)
}
@EventHandler
def init(event:FMLInitializationEvent)
{
MultipartProxy.init()
}
@EventHandler
def postInit(event:FMLPostInitializationEvent)
{
if(MultiPartRegistry.required)
{
MultiPartRegistry.postInit()
MultipartProxy.postInit()
}
}
@EventHandler
def beforeServerStart(event:FMLServerAboutToStartEvent)
{
MultiPartRegistry.beforeServerStart()
}
}

View file

@ -0,0 +1,76 @@
package codechicken.multipart.handler
import net.minecraft.tileentity.TileEntity
import net.minecraft.nbt.NBTTagCompound
import java.util.Map
import net.minecraft.world.chunk.Chunk
import net.minecraft.world.ChunkPosition
import codechicken.multipart.TileMultipart
import codechicken.lib.asm.ObfMapping
import net.minecraft.world.World
/**
* Hack due to lack of TileEntityLoadEvent in forge
*/
object MultipartSaveLoad
{
var loadingWorld:World = _
class TileNBTContainer extends TileEntity
{
var tag:NBTTagCompound = _
override def readFromNBT(t:NBTTagCompound)
{
super.readFromNBT(t)
tag = t
}
}
def hookLoader()
{
val field = classOf[TileEntity].getDeclaredField(
new ObfMapping("net/minecraft/tileentity/TileEntity", "nameToClassMap", "Ljava/util/Map;")
.toRuntime.s_name)
field.setAccessible(true)
val map = field.get(null).asInstanceOf[Map[String, Class[_ <: TileEntity]]]
map.put("savedMultipart", classOf[TileNBTContainer])
}
private val classToNameMap = getClassToNameMap
def registerTileClass(t:Class[_ <: TileEntity])
{
classToNameMap.put(t, "savedMultipart")
}
def getClassToNameMap =
{
val field = classOf[TileEntity].getDeclaredField(
new ObfMapping("net/minecraft/tileentity/TileEntity", "classToNameMap", "Ljava/util/Map;")
.toRuntime.s_name)
field.setAccessible(true)
field.get(null).asInstanceOf[Map[Class[_ <: TileEntity], String]]
}
def loadTiles(chunk:Chunk)
{
loadingWorld = chunk.worldObj
val iterator = chunk.chunkTileEntityMap.asInstanceOf[Map[ChunkPosition, TileEntity]].entrySet.iterator
while(iterator.hasNext)
{
val e = iterator.next
if(e.getValue.isInstanceOf[TileNBTContainer])
{
val t = TileMultipart.createFromNBT(e.getValue.asInstanceOf[TileNBTContainer].tag)
if(t != null)
{
t.setWorldObj(e.getValue.worldObj)
t.validate()
e.setValue(t)
}
else
iterator.remove()
}
}
}
}

View file

@ -0,0 +1,174 @@
package codechicken.multipart.handler
import codechicken.lib.packet.PacketCustom.IClientPacketHandler
import codechicken.lib.packet.PacketCustom.IServerPacketHandler
import codechicken.lib.packet.PacketCustom
import net.minecraft.client.multiplayer.NetClientHandler
import net.minecraft.client.Minecraft
import codechicken.multipart.MultiPartRegistry
import net.minecraft.network.NetServerHandler
import net.minecraft.entity.player.EntityPlayerMP
import codechicken.multipart.ControlKeyModifer
import net.minecraft.network.packet.Packet255KickDisconnect
import net.minecraft.world.World
import net.minecraft.world.chunk.Chunk
import java.util.Map
import java.util.Iterator
import net.minecraft.tileentity.TileEntity
import codechicken.multipart.TileMultipart
import net.minecraft.entity.player.EntityPlayer
import scala.collection.mutable.{Map => MMap}
import codechicken.lib.vec.BlockCoord
import codechicken.lib.data.MCOutputStreamWrapper
import java.io.DataOutputStream
import java.io.ByteArrayOutputStream
import net.minecraft.world.WorldServer
import net.minecraft.world.ChunkCoordIntPair
import MultipartProxy._
import codechicken.multipart.PacketScheduler
class MultipartPH
{
val channel = MultipartMod
val registryChannel = "ForgeMultipart"//Must use the 250 system for ID registry as the NetworkMod idMap hasn't been properly initialized from the server yet.
}
object MultipartCPH extends MultipartPH with IClientPacketHandler
{
def handlePacket(packet:PacketCustom, netHandler:NetClientHandler, mc:Minecraft)
{
packet.getType match
{
case 1 => handlePartRegistration(packet, netHandler)
case 2 => handleCompressedTileDesc(packet, mc.theWorld)
case 3 => handleCompressedTileData(packet, mc.theWorld)
}
}
def handlePartRegistration(packet:PacketCustom, netHandler:NetClientHandler)
{
val missing = MultiPartRegistry.readIDMap(packet)
if(!missing.isEmpty)
netHandler.handleKickDisconnect(new Packet255KickDisconnect("The following multiparts are not installed on this client: "+missing.mkString(", ")))
}
def handleCompressedTileDesc(packet:PacketCustom, world:World)
{
val cc = new ChunkCoordIntPair(packet.readInt, packet.readInt)
val num = packet.readUShort
for(i <- 0 until num)
TileMultipart.handleDescPacket(world, indexInChunk(cc, packet.readShort), packet)
}
def handleCompressedTileData(packet:PacketCustom, world:World)
{
var x = packet.readInt
while(x != Int.MaxValue)
{
val pos = new BlockCoord(x, packet.readInt, packet.readInt)
var i = packet.readUByte
while(i < 255)
{
TileMultipart.handlePacket(pos, world, i, packet)
i = packet.readUByte
}
x = packet.readInt
}
TileMultipart.flushClientCache()
}
}
object MultipartSPH extends MultipartPH with IServerPacketHandler
{
class MCByteStream(bout:ByteArrayOutputStream) extends MCOutputStreamWrapper(new DataOutputStream(bout))
{
def getBytes = bout.toByteArray
}
private val updateMap = MMap[World, MMap[BlockCoord, MCByteStream]]()
def handlePacket(packet:PacketCustom, netHandler:NetServerHandler, sender:EntityPlayerMP)
{
packet.getType match
{
case 1 => ControlKeyModifer.map.put(sender, packet.readBoolean)
}
}
def onWorldUnload(world:World)
{
if(!world.isRemote)
updateMap.remove(world)
}
def getTileStream(world:World, pos:BlockCoord) =
updateMap.getOrElseUpdate(world, {
if(world.isRemote)
throw new IllegalArgumentException("Cannot use MultipartSPH on a client world")
MMap()
}).getOrElseUpdate(pos, {
val s = new MCByteStream(new ByteArrayOutputStream)
s.writeCoord(pos)
s
})
def onTickEnd(players:Seq[EntityPlayerMP])
{
PacketScheduler.sendScheduled()
players.foreach{p =>
val m = updateMap.getOrElse(p.worldObj, null)
if(m != null && !m.isEmpty)
{
val manager = p.worldObj.asInstanceOf[WorldServer].getPlayerManager
val packet = new PacketCustom(channel, 3).setChunkDataPacket().compressed()
var send = false
m.foreach(e =>
if(manager.isPlayerWatchingChunk(p, e._1.x>>4, e._1.z>>4))
{
send = true
packet.writeByteArray(e._2.getBytes)
packet.writeByte(255)//terminator
})
if(send)
{
packet.writeInt(Int.MaxValue)//terminator
packet.sendToPlayer(p)
}
}
}
updateMap.foreach(_._2.clear())
}
def onChunkWatch(player:EntityPlayer, chunk:Chunk)
{
val pkt = getDescPacket(chunk, chunk.chunkTileEntityMap.asInstanceOf[Map[_, TileEntity]].values.iterator)
if(pkt != null)
pkt.sendToPlayer(player)
}
def getDescPacket(chunk:Chunk, it:Iterator[TileEntity]):PacketCustom =
{
val s = new MCByteStream(new ByteArrayOutputStream)
var num = 0
while(it.hasNext)
{
val tile = it.next
if(tile.isInstanceOf[TileMultipart])
{
s.writeShort(indexInChunk(new BlockCoord(tile)))
tile.asInstanceOf[TileMultipart].writeDesc(s)
num+=1
}
}
if(num != 0)
{
return new PacketCustom(channel, 2).setChunkDataPacket().compressed()
.writeInt(chunk.xPosition).writeInt(chunk.zPosition)
.writeShort(num)
.writeByteArray(s.getBytes)
}
return null
}
}

View file

@ -0,0 +1,100 @@
package codechicken.multipart.handler
import codechicken.multipart.BlockMultipart
import cpw.mods.fml.client.registry.RenderingRegistry
import cpw.mods.fml.client.registry.ClientRegistry
import net.minecraft.tileentity.TileEntity
import codechicken.lib.config.ConfigFile
import java.io.File
import codechicken.multipart.handler.MultipartProxy._
import codechicken.multipart.MultipartRenderer
import net.minecraftforge.common.MinecraftForge
import codechicken.multipart.MultipartGenerator
import cpw.mods.fml.relauncher.SideOnly
import cpw.mods.fml.relauncher.Side
import codechicken.lib.packet.PacketCustom
import cpw.mods.fml.client.registry.KeyBindingRegistry
import codechicken.multipart.ControlKeyHandler
import cpw.mods.fml.common.network.NetworkRegistry
import cpw.mods.fml.common.registry.TickRegistry
import net.minecraft.block.Block
import net.minecraft.world.ChunkCoordIntPair
import codechicken.lib.vec.BlockCoord
import codechicken.lib.world.WorldExtensionManager
import codechicken.multipart.TickScheduler
class MultipartProxy_serverImpl
{
def preInit(cfgdir:File)
{
config = new ConfigFile(new File(cfgdir, "multipart.cfg"))
.setComment("Multipart API config file")
MultipartGenerator.registerTrait("codechicken.multipart.TSlottedPart", "codechicken.multipart.scalatraits.TSlottedTile")
MultipartGenerator.registerTrait("net.minecraftforge.fluids.IFluidHandler", "codechicken.multipart.scalatraits.TFluidHandlerTile")
MultipartGenerator.registerTrait("codechicken.multipart.JPartialOcclusion", "codechicken.multipart.scalatraits.TPartialOcclusionTile")
MultipartGenerator.registerTrait("codechicken.multipart.IRedstonePart", "codechicken.multipart.scalatraits.TRedstoneTile")
MultipartGenerator.registerTrait("codechicken.multipart.IRandomDisplayTick", "codechicken.multipart.scalatraits.TRandomDisplayTickTile", null)
MultipartGenerator.registerTrait("codechicken.multipart.INeighborTileChange", null, "codechicken.multipart.scalatraits.TTileChangeTile")
MultipartSaveLoad.hookLoader()
}
def init()
{
block = new BlockMultipart(config.getTag("block.id").getIntValue(getFreeBlockID(1281)))
block.setUnlocalizedName("ccmultipart")
}
def postInit()
{
MinecraftForge.EVENT_BUS.register(MultipartEventHandler)
PacketCustom.assignHandler(MultipartSPH.channel, MultipartSPH)
NetworkRegistry.instance.registerConnectionHandler(MultipartEventHandler)
TickRegistry.registerTickHandler(MultipartEventHandler, Side.SERVER)
WorldExtensionManager.registerWorldExtension(TickScheduler)
}
def getFreeBlockID(preferred:Int):Int =
{
for(i <- (preferred until 4096) ++ (preferred-1 until 255))
if(Block.blocksList(i) == null)
return i
throw new RuntimeException("Out of Block IDs")
}
def onTileClassBuilt(t:Class[_ <: TileEntity])
{
MultipartSaveLoad.registerTileClass(t)
}
}
class MultipartProxy_clientImpl extends MultipartProxy_serverImpl
{
@SideOnly(Side.CLIENT)
override def postInit()
{
super.postInit()
RenderingRegistry.registerBlockHandler(MultipartRenderer)
PacketCustom.assignHandler(MultipartCPH.channel, MultipartCPH)
PacketCustom.assignHandler(MultipartCPH.registryChannel, 1, 127, MultipartCPH)
KeyBindingRegistry.registerKeyBinding(ControlKeyHandler)
}
@SideOnly(Side.CLIENT)
override def onTileClassBuilt(t:Class[_ <: TileEntity])
{
super.onTileClassBuilt(t)
ClientRegistry.bindTileEntitySpecialRenderer(t, MultipartRenderer)
}
}
object MultipartProxy extends MultipartProxy_clientImpl
{
var block:BlockMultipart = _
var config:ConfigFile = _
def indexInChunk(cc:ChunkCoordIntPair, i:Int) = new BlockCoord(cc.chunkXPos<<4|i&0xF, (i>>8)&0xFF, cc.chunkZPos<<4|(i&0xF0)>>4)
def indexInChunk(pos:BlockCoord) = pos.x&0xF|pos.y<<8|(pos.z&0xF)<<4
}

View file

@ -0,0 +1,183 @@
package codechicken.multipart.minecraft;
import codechicken.lib.vec.BlockCoord;
import codechicken.lib.vec.Cuboid6;
import codechicken.lib.vec.Vector3;
import codechicken.multipart.IFaceRedstonePart;
import codechicken.multipart.TickScheduler;
import net.minecraft.block.Block;
import net.minecraft.block.BlockButton;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.projectile.EntityArrow;
import net.minecraft.item.ItemStack;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.world.World;
import net.minecraftforge.common.ForgeDirection;
public class ButtonPart extends McSidedMetaPart implements IFaceRedstonePart
{
public static BlockButton stoneButton = (BlockButton) Block.stoneButton;
public static BlockButton woodenButton = (BlockButton) Block.woodenButton;
public static int[] metaSideMap = new int[]{-1, 4, 5, 2, 3};
public static int[] sideMetaMap = new int[]{-1, -1, 3, 4, 1, 2};
public static BlockButton getButton(int meta)
{
return (meta&0x10) > 0 ? woodenButton : stoneButton;
}
public ButtonPart()
{
}
public ButtonPart(int meta)
{
super(meta);
}
@Override
public int sideForMeta(int meta)
{
return metaSideMap[meta&7];
}
@Override
public Block getBlock()
{
return getButton(meta);
}
@Override
public String getType()
{
return "mc_button";
}
public int delay()
{
return sensitive() ? 30 : 20;
}
public boolean sensitive()
{
return (meta&0x10) > 0;
}
@Override
public Cuboid6 getBounds()
{
int m = meta & 7;
double d = pressed() ? 0.0625 : 0.125;
if (m == 1)
return new Cuboid6(0.0, 0.375, 0.5 - 0.1875, d, 0.625, 0.5 + 0.1875);
if (m == 2)
return new Cuboid6(1.0 - d, 0.375, 0.5 - 0.1875, 1.0, 0.625, 0.5 + 0.1875);
if (m == 3)
return new Cuboid6(0.5 - 0.1875, 0.375, 0.0, 0.5 + 0.1875, 0.625, d);
if (m == 4)
return new Cuboid6(0.5 - 0.1875, 0.375, 1.0 - d, 0.5 + 0.1875, 0.625, 1.0);
return null;//falloff
}
public static McBlockPart placement(World world, BlockCoord pos, int side, int type)
{
if(side == 0 || side == 1)
return null;
pos = pos.copy().offset(side^1);
if(!world.isBlockSolidOnSide(pos.x, pos.y, pos.z, ForgeDirection.getOrientation(side)))
return null;
return new ButtonPart(sideMetaMap[side^1]|type<<4);
}
@Override
public boolean activate(EntityPlayer player, MovingObjectPosition part, ItemStack item)
{
if(pressed())
return false;
if(!world().isRemote)
toggle();
return true;
}
@Override
public void scheduledTick()
{
if(pressed())
updateState();
}
public boolean pressed()
{
return (meta&8) > 0;
}
@Override
public void onEntityCollision(Entity entity)
{
if(!pressed() && !world().isRemote && entity instanceof EntityArrow)
updateState();
}
private void toggle()
{
boolean in = !pressed();
meta^=8;
world().playSoundEffect(x() + 0.5, y() + 0.5, z() + 0.5, "random.click", 0.3F, in ? 0.6F : 0.5F);
if(in)
scheduleTick(delay());
sendDescUpdate();
tile().notifyPartChange(this);
tile().notifyNeighborChange(metaSideMap[meta&7]);
tile().markDirty();
}
private void updateState()
{
boolean arrows = sensitive() && !world().getEntitiesWithinAABB(EntityArrow.class,
getBounds().add(Vector3.fromTileEntity(tile())).toAABB()).isEmpty();
boolean pressed = pressed();
if(arrows != pressed)
toggle();
if(arrows && pressed)
scheduleTick(delay());
}
@Override
public void onRemoved()
{
if(pressed())
tile().notifyNeighborChange(metaSideMap[meta&7]);
}
@Override
public int weakPowerLevel(int side)
{
return pressed() ? 15 : 0;
}
@Override
public int strongPowerLevel(int side)
{
return pressed() && side == metaSideMap[meta&7] ? 15 : 0;
}
@Override
public boolean canConnectRedstone(int side)
{
return true;
}
@Override
public int getFace() {
return metaSideMap[meta&7];
}
}

View file

@ -0,0 +1,66 @@
package codechicken.multipart.minecraft;
import net.minecraft.block.Block;
import net.minecraft.world.World;
import codechicken.lib.vec.BlockCoord;
import codechicken.multipart.MultiPartRegistry.IPartConverter;
import codechicken.multipart.MultiPartRegistry.IPartFactory;
import codechicken.multipart.MultiPartRegistry;
import codechicken.multipart.TMultiPart;
public class Content implements IPartFactory, IPartConverter
{
@Override
public TMultiPart createPart(String name, boolean client)
{
if(name.equals("mc_torch")) return new TorchPart();
if(name.equals("mc_lever")) return new LeverPart();
if(name.equals("mc_button")) return new ButtonPart();
if(name.equals("mc_redtorch")) return new RedstoneTorchPart();
return null;
}
public void init()
{
MultiPartRegistry.registerConverter(this);
MultiPartRegistry.registerParts(this, new String[]{
"mc_torch",
"mc_lever",
"mc_button",
"mc_redtorch"
});
}
@Override
public boolean canConvert(int blockID)
{
return blockID == Block.torchWood.blockID ||
blockID == Block.lever.blockID ||
blockID == Block.stoneButton.blockID ||
blockID == Block.woodenButton.blockID ||
blockID == Block.torchRedstoneIdle.blockID ||
blockID == Block.torchRedstoneActive.blockID;
}
@Override
public TMultiPart convert(World world, BlockCoord pos)
{
int id = world.getBlockId(pos.x, pos.y, pos.z);
int meta = world.getBlockMetadata(pos.x, pos.y, pos.z);
if(id == Block.torchWood.blockID)
return new TorchPart(meta);
if(id == Block.lever.blockID)
return new LeverPart(meta);
if(id == Block.stoneButton.blockID)
return new ButtonPart(meta);
if(id == Block.woodenButton.blockID)
return new ButtonPart(meta|0x10);
if(id == Block.torchRedstoneIdle.blockID)
return new RedstoneTorchPart(meta);
if(id == Block.torchRedstoneActive.blockID)
return new RedstoneTorchPart(meta|0x10);
return null;
}
}

View file

@ -0,0 +1,118 @@
package codechicken.multipart.minecraft;
import codechicken.lib.packet.PacketCustom;
import codechicken.lib.raytracer.RayTracer;
import codechicken.lib.vec.BlockCoord;
import codechicken.lib.vec.Vector3;
import codechicken.multipart.TileMultipart;
import net.minecraft.block.Block;
import net.minecraft.block.BlockFence;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import net.minecraft.network.packet.Packet15Place;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.world.World;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.ForgeSubscribe;
import net.minecraftforge.event.entity.player.PlayerDestroyItemEvent;
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
import net.minecraftforge.event.entity.player.PlayerInteractEvent.Action;
public class EventHandler
{
private ThreadLocal<Object> placing = new ThreadLocal<Object>();
@ForgeSubscribe
public void playerInteract(PlayerInteractEvent event)
{
if(event.action == Action.RIGHT_CLICK_BLOCK && event.entityPlayer.worldObj.isRemote)
{
if(placing.get() != null)
return;//for mods that do dumb stuff and call this event like MFR
placing.set(event);
if(place(event.entityPlayer, event.entityPlayer.worldObj))
event.setCanceled(true);
placing.set(null);
}
}
public static boolean place(EntityPlayer player, World world)
{
MovingObjectPosition hit = RayTracer.reTrace(world, player);
if(hit == null)
return false;
BlockCoord pos = new BlockCoord(hit.blockX, hit.blockY, hit.blockZ).offset(hit.sideHit);
ItemStack held = player.getHeldItem();
McBlockPart part = null;
if(held == null)
return false;
if(held.itemID == Block.torchWood.blockID)
part = TorchPart.placement(world, pos, hit.sideHit);
else if(held.itemID == Block.lever.blockID)
part = LeverPart.placement(world, pos, player, hit.sideHit);
else if(held.itemID == Block.stoneButton.blockID)
part = ButtonPart.placement(world, pos, hit.sideHit, 0);
else if(held.itemID == Block.woodenButton.blockID)
part = ButtonPart.placement(world, pos, hit.sideHit, 1);
else if(held.itemID == Block.torchRedstoneActive.blockID)
part = RedstoneTorchPart.placement(world, pos, hit.sideHit);
if(part == null)
return false;
if(world.isRemote && !player.isSneaking())//attempt to use block activated like normal and tell the server the right stuff
{
Vector3 f = new Vector3(hit.hitVec).add(-hit.blockX, -hit.blockY, -hit.blockZ);
Block block = Block.blocksList[world.getBlockId(hit.blockX, hit.blockY, hit.blockZ)];
if(block != null && !ignoreActivate(block) && block.onBlockActivated(world, hit.blockX, hit.blockY, hit.blockZ, player, hit.sideHit, (float)f.x, (float)f.y, (float)f.z))
{
player.swingItem();
PacketCustom.sendToServer(new Packet15Place(
hit.blockX, hit.blockY, hit.blockZ, hit.sideHit,
player.inventory.getCurrentItem(),
(float)f.x, (float)f.y, (float)f.z));
return false;
}
}
TileMultipart tile = TileMultipart.getOrConvertTile(world, pos);
if(tile == null || !tile.canAddPart(part))
return false;
if(!world.isRemote)
{
TileMultipart.addPart(world, pos, part);
world.playSoundEffect(pos.x + 0.5, pos.y + 0.5, pos.z + 0.5,
part.getBlock().stepSound.getPlaceSound(),
(part.getBlock().stepSound.getVolume() + 1.0F) / 2.0F,
part.getBlock().stepSound.getPitch() * 0.8F);
if(!player.capabilities.isCreativeMode)
{
held.stackSize--;
if (held.stackSize == 0)
{
player.inventory.mainInventory[player.inventory.currentItem] = null;
MinecraftForge.EVENT_BUS.post(new PlayerDestroyItemEvent(player, held));
}
}
}
else
{
player.swingItem();
new PacketCustom(McMultipartSPH.channel, 1).sendToServer();
}
return true;
}
/**
* Because vanilla is weird.
*/
private static boolean ignoreActivate(Block block)
{
if(block instanceof BlockFence)
return true;
return false;
}
}

View file

@ -0,0 +1,15 @@
package codechicken.multipart.minecraft;
import codechicken.lib.vec.BlockCoord;
import net.minecraft.world.World;
public interface IPartMeta
{
public int getMetadata();
public World getWorld();
public int getBlockId();
public BlockCoord getPos();
}

View file

@ -0,0 +1,151 @@
package codechicken.multipart.minecraft;
import net.minecraft.block.Block;
import net.minecraft.block.BlockLever;
import net.minecraft.client.renderer.RenderBlocks;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World;
import net.minecraftforge.common.ForgeDirection;
import codechicken.lib.vec.BlockCoord;
import codechicken.lib.vec.Cuboid6;
import codechicken.multipart.IFaceRedstonePart;
public class LeverPart extends McSidedMetaPart implements IFaceRedstonePart
{
public static BlockLever lever = (BlockLever) Block.lever;
public static int[] metaSideMap = new int[]{1, 4, 5, 2, 3, 0, 0, 1};
public static int[] sideMetaMap = new int[]{6, 0, 3, 4, 1, 2};
public static int[] metaSwapMap = new int[]{5, 7};
public LeverPart()
{
}
public LeverPart(int meta)
{
super(meta);
}
@Override
public Block getBlock()
{
return lever;
}
@Override
public String getType()
{
return "mc_lever";
}
public boolean active()
{
return (meta&8) > 0;
}
@Override
public Cuboid6 getBounds()
{
int m = meta & 7;
double d = 0.1875;
if (m == 1)
return new Cuboid6(0, 0.2, 0.5 - d, d * 2, 0.8, 0.5 + d);
if (m == 2)
return new Cuboid6(1 - d * 2, 0.2, 0.5 - d, 1, 0.8, 0.5 + d);
if (m == 3)
return new Cuboid6(0.5 - d, 0.2, 0, 0.5 + d, 0.8, d * 2);
if (m == 4)
return new Cuboid6(0.5 - d, 0.2, 1 - d * 2, 0.5 + d, 0.8, 1);
d = 0.25;
if (m == 0 || m == 7)
return new Cuboid6(0.5 - d, 0.4, 0.5 - d, 0.5 + d, 1, 0.5 + d);
return new Cuboid6(0.5 - d, 0, 0.5 - d, 0.5 + d, 0.6, 0.5 + d);
}
@Override
public int sideForMeta(int meta)
{
return metaSideMap[meta&7];
}
public static McBlockPart placement(World world, BlockCoord pos, EntityPlayer player, int side)
{
pos = pos.copy().offset(side^1);
if(!world.isBlockSolidOnSide(pos.x, pos.y, pos.z, ForgeDirection.getOrientation(side)))
return null;
int meta = sideMetaMap[side^1];
if(side < 2 && ((int)(player.rotationYaw / 90 + 0.5) & 1) == 0)
meta = metaSwapMap[side^1];
return new LeverPart(meta);
}
@Override
public boolean activate(EntityPlayer player, MovingObjectPosition part, ItemStack item)
{
World world = world();
if(world.isRemote)
return true;
world.playSoundEffect(x() + 0.5, y() + 0.5, z() + 0.5, "random.click", 0.3F, !active() ? 0.6F : 0.5F);
meta ^= 8;
sendDescUpdate();
tile().notifyPartChange(this);
tile().notifyNeighborChange(metaSideMap[meta&7]);
tile().markDirty();
return true;
}
@Override
public void drawBreaking(RenderBlocks renderBlocks)
{
IBlockAccess actual = renderBlocks.blockAccess;
renderBlocks.blockAccess = new PartMetaAccess(this);
renderBlocks.renderBlockLever(lever, x(), y(), z());
renderBlocks.blockAccess = actual;
}
@Override
public void onRemoved()
{
if(active())
tile().notifyNeighborChange(metaSideMap[meta&7]);
}
@Override
public void onConverted()
{
if(active())
tile().notifyNeighborChange(metaSideMap[meta&7]);
}
@Override
public int weakPowerLevel(int side)
{
return active() ? 15 : 0;
}
@Override
public int strongPowerLevel(int side)
{
return active() && side == metaSideMap[meta&7] ? 15 : 0;
}
@Override
public boolean canConnectRedstone(int side)
{
return true;
}
@Override
public int getFace() {
return metaSideMap[meta&7];
}
}

View file

@ -0,0 +1,92 @@
package codechicken.multipart.minecraft;
import java.util.Arrays;
import java.util.Collections;
import net.minecraft.block.Block;
import net.minecraft.client.particle.EffectRenderer;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Icon;
import net.minecraft.util.MovingObjectPosition;
import codechicken.lib.vec.Cuboid6;
import codechicken.multipart.IconHitEffects;
import codechicken.multipart.JCuboidPart;
import codechicken.multipart.JIconHitEffects;
import codechicken.multipart.JNormalOcclusion;
import codechicken.multipart.NormalOcclusionTest;
import codechicken.multipart.TMultiPart;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
public abstract class McBlockPart extends JCuboidPart implements JNormalOcclusion, JIconHitEffects
{
public abstract Block getBlock();
@Override
public boolean occlusionTest(TMultiPart npart)
{
return NormalOcclusionTest.apply(this, npart);
}
@Override
public Iterable<Cuboid6> getOcclusionBoxes()
{
return Arrays.asList(getBounds());
}
@Override
public Iterable<Cuboid6> getCollisionBoxes()
{
return Collections.emptyList();
}
@Override
public Iterable<ItemStack> getDrops()
{
return Arrays.asList(new ItemStack(getBlock()));
}
@Override
public ItemStack pickItem(MovingObjectPosition hit)
{
return new ItemStack(getBlock());
}
@Override
public float getStrength(MovingObjectPosition hit, EntityPlayer player)
{
return getBlock().getPlayerRelativeBlockHardness(player, player.worldObj, hit.blockX, hit.blockY, hit.blockZ)*30;
}
@Override
public Icon getBreakingIcon(Object subPart, int side)
{
return getBlock().getIcon(0, 0);
}
@Override
@SideOnly(Side.CLIENT)
public Icon getBrokenIcon(int side)
{
return getBlock().getIcon(0, 0);
}
@Override
public void addHitEffects(MovingObjectPosition hit, EffectRenderer effectRenderer)
{
IconHitEffects.addHitEffects(this, hit, effectRenderer);
}
@Override
public void addDestroyEffects(MovingObjectPosition hit, EffectRenderer effectRenderer)
{
IconHitEffects.addDestroyEffects(this, effectRenderer, false);
}
@Override
public int getLightValue()
{
return Block.lightValue[getBlock().blockID];
}
}

View file

@ -0,0 +1,85 @@
package codechicken.multipart.minecraft;
import codechicken.lib.data.MCDataInput;
import codechicken.lib.data.MCDataOutput;
import codechicken.lib.lighting.LazyLightMatrix;
import codechicken.lib.vec.BlockCoord;
import codechicken.lib.vec.Vector3;
import net.minecraft.client.renderer.RenderBlocks;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.world.World;
public abstract class McMetaPart extends McBlockPart implements IPartMeta
{
public byte meta;
public McMetaPart()
{
}
public McMetaPart(int meta)
{
this.meta = (byte)meta;
}
@Override
public void save(NBTTagCompound tag)
{
tag.setByte("meta", meta);
}
@Override
public void load(NBTTagCompound tag)
{
meta = tag.getByte("meta");
}
@Override
public void writeDesc(MCDataOutput packet)
{
packet.writeByte(meta);
}
@Override
public void readDesc(MCDataInput packet)
{
meta = packet.readByte();
}
@Override
public World getWorld()
{
return world();
}
@Override
public int getMetadata()
{
return meta;
}
@Override
public int getBlockId()
{
return getBlock().blockID;
}
@Override
public BlockCoord getPos()
{
return new BlockCoord(tile());
}
@Override
public boolean doesTick()
{
return false;
}
@Override
public void renderStatic(Vector3 pos, LazyLightMatrix olm, int pass)
{
if(pass == 0)
new RenderBlocks(new PartMetaAccess(this)).renderBlockByRenderType(getBlock(), x(), y(), z());
}
}

View file

@ -0,0 +1,33 @@
package codechicken.multipart.minecraft;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.NetClientHandler;
import net.minecraft.client.multiplayer.WorldClient;
import codechicken.lib.packet.PacketCustom;
import codechicken.lib.packet.PacketCustom.IClientPacketHandler;
import codechicken.lib.vec.BlockCoord;
public class McMultipartCPH implements IClientPacketHandler
{
public static Object channel = MinecraftMultipartMod.instance;
@Override
public void handlePacket(PacketCustom packet, NetClientHandler nethandler, Minecraft mc)
{
switch(packet.getType())
{
case 1:
spawnBurnoutSmoke(mc.theWorld, packet.readCoord());
break;
}
}
private void spawnBurnoutSmoke(WorldClient world, BlockCoord pos)
{
for(int l = 0; l < 5; l++)
world.spawnParticle("smoke",
pos.x+world.rand.nextDouble()*0.6+0.2,
pos.y+world.rand.nextDouble()*0.6+0.2,
pos.z+world.rand.nextDouble()*0.6+0.2, 0, 0, 0);
}
}

View file

@ -0,0 +1,28 @@
package codechicken.multipart.minecraft;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.network.NetServerHandler;
import net.minecraft.world.World;
import codechicken.lib.packet.PacketCustom;
import codechicken.lib.packet.PacketCustom.IServerPacketHandler;
public class McMultipartSPH implements IServerPacketHandler
{
public static Object channel = MinecraftMultipartMod.instance;
@Override
public void handlePacket(PacketCustom packet, NetServerHandler nethandler, EntityPlayerMP sender)
{
switch(packet.getType())
{
case 1:
EventHandler.place(sender, sender.worldObj);
break;
}
}
public static void spawnBurnoutSmoke(World world, int x, int y, int z)
{
new PacketCustom(channel, 1).writeCoord(x, y, z).sendToChunk(world, x>>4, z>>4);
}
}

View file

@ -0,0 +1,69 @@
package codechicken.multipart.minecraft;
import net.minecraft.item.ItemStack;
import net.minecraftforge.common.ForgeDirection;
import codechicken.lib.vec.BlockCoord;
import codechicken.lib.vec.Vector3;
import codechicken.multipart.TFacePart;
import codechicken.multipart.TileMultipart;
public abstract class McSidedMetaPart extends McMetaPart implements TFacePart
{
public McSidedMetaPart()
{
}
public McSidedMetaPart(int meta)
{
super(meta);
}
public abstract int sideForMeta(int meta);
@Override
public void onNeighborChanged()
{
if(!world().isRemote)
dropIfCantStay();
}
public boolean canStay()
{
BlockCoord pos = new BlockCoord(tile()).offset(sideForMeta(meta));
return world().isBlockSolidOnSide(pos.x, pos.y, pos.z, ForgeDirection.getOrientation(sideForMeta(meta)^1));
}
public boolean dropIfCantStay()
{
if(!canStay())
{
drop();
return true;
}
return false;
}
public void drop()
{
TileMultipart.dropItem(new ItemStack(getBlock()), world(), Vector3.fromTileEntityCenter(tile()));
tile().remPart(this);
}
@Override
public int getSlotMask()
{
return 1<<sideForMeta(meta);
}
@Override
public boolean solid(int side)
{
return false;
}
@Override
public int redstoneConductionMap()
{
return 0x1F;
}
}

View file

@ -0,0 +1,29 @@
package codechicken.multipart.minecraft;
import net.minecraftforge.common.MinecraftForge;
import codechicken.lib.packet.PacketCustom;
import codechicken.lib.packet.PacketCustom.CustomTinyPacketHandler;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.Mod.Instance;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import cpw.mods.fml.common.network.NetworkMod;
@Mod(modid = "McMultipart", acceptedMinecraftVersions="[1.6.4]")
@NetworkMod(clientSideRequired = true, serverSideRequired = true, tinyPacketHandler=CustomTinyPacketHandler.class)
public class MinecraftMultipartMod
{
@Instance("McMultipart")
public static MinecraftMultipartMod instance;
@Mod.EventHandler
public void preInit(FMLPreInitializationEvent event)
{
new Content().init();
MinecraftForge.EVENT_BUS.register(new EventHandler());
PacketCustom.assignHandler(this, new McMultipartSPH());
if(FMLCommonHandler.instance().getSide().isClient())
PacketCustom.assignHandler(this, new McMultipartCPH());
}
}

View file

@ -0,0 +1,141 @@
package codechicken.multipart.minecraft;
import codechicken.lib.vec.BlockCoord;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.Vec3Pool;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.biome.BiomeGenBase;
import net.minecraftforge.common.ForgeDirection;
public class PartMetaAccess implements IBlockAccess
{
public IPartMeta part;
private BlockCoord pos;
public PartMetaAccess(IPartMeta p)
{
part = p;
pos = p.getPos();
}
@Override
public int getBlockId(int i, int j, int k)
{
if(i == pos.x && j == pos.y && k == pos.z)
return part.getBlockId();
return part.getWorld().getBlockId(i, j, k);
}
@Override
public TileEntity getBlockTileEntity(int i, int j, int k)
{
if(i == pos.x && j == pos.y && k == pos.z)
throw new IllegalArgumentException("Unsupported Operation");
return part.getWorld().getBlockTileEntity(i, j, k);
}
@Override
@SideOnly(Side.CLIENT)
public int getLightBrightnessForSkyBlocks(int i, int j, int k, int l)
{
return part.getWorld().getLightBrightnessForSkyBlocks(i, j, k, l);
}
@Override
public int getBlockMetadata(int i, int j, int k)
{
if(i == pos.x && j == pos.y && k == pos.z)
return part.getMetadata()&0xF;
return part.getWorld().getBlockMetadata(i, j, k);
}
@Override
@SideOnly(Side.CLIENT)
public float getBrightness(int i, int j, int k, int l)
{
return part.getWorld().getBrightness(i, j, k, l);
}
@Override
@SideOnly(Side.CLIENT)
public float getLightBrightness(int i, int j, int k)
{
return part.getWorld().getLightBrightness(i, j, k);
}
@Override
public Material getBlockMaterial(int i, int j, int k)
{
return Block.blocksList[getBlockId(i, j, k)].blockMaterial;
}
@Override
@SideOnly(Side.CLIENT)
public boolean isBlockOpaqueCube(int i, int j, int k)
{
return part.getWorld().isBlockOpaqueCube(i, j, k);
}
@Override
public boolean isBlockNormalCube(int i, int j, int k)
{
return part.getWorld().isBlockNormalCube(i, j, k);
}
@Override
@SideOnly(Side.CLIENT)
public boolean isAirBlock(int i, int j, int k)
{
throw new IllegalArgumentException("Unsupported Operation");
}
@Override
@SideOnly(Side.CLIENT)
public BiomeGenBase getBiomeGenForCoords(int i, int j)
{
return part.getWorld().getBiomeGenForCoords(i, j);
}
@Override
@SideOnly(Side.CLIENT)
public int getHeight()
{
return part.getWorld().getHeight();
}
@Override
@SideOnly(Side.CLIENT)
public boolean extendedLevelsInChunkCache()
{
return part.getWorld().extendedLevelsInChunkCache();
}
@Override
@SideOnly(Side.CLIENT)
public boolean doesBlockHaveSolidTopSurface(int i, int j, int k)
{
throw new IllegalArgumentException("Unsupported Operation");
}
@Override
public Vec3Pool getWorldVec3Pool()
{
return part.getWorld().getWorldVec3Pool();
}
@Override
public int isBlockProvidingPowerTo(int i, int j, int k, int l)
{
throw new IllegalArgumentException("Unsupported Operation");
}
@Override
public boolean isBlockSolidOnSide(int x, int y, int z, ForgeDirection side, boolean _default)
{
return part.getWorld().isBlockSolidOnSide(x, y, z, side, _default);
}
}

View file

@ -0,0 +1,245 @@
package codechicken.multipart.minecraft;
import java.util.Random;
import net.minecraft.block.Block;
import net.minecraft.block.BlockRedstoneTorch;
import net.minecraft.item.ItemStack;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.world.World;
import net.minecraftforge.common.ForgeDirection;
import codechicken.lib.vec.BlockCoord;
import codechicken.lib.vec.Cuboid6;
import codechicken.multipart.IFaceRedstonePart;
import codechicken.multipart.IRandomUpdateTick;
import codechicken.multipart.RedstoneInteractions;
public class RedstoneTorchPart extends TorchPart implements IFaceRedstonePart, IRandomUpdateTick
{
public static BlockRedstoneTorch torchActive = (BlockRedstoneTorch) Block.torchRedstoneActive;
public static BlockRedstoneTorch torchIdle = (BlockRedstoneTorch) Block.torchRedstoneIdle;
public class BurnoutEntry
{
public BurnoutEntry(long l)
{
timeout = l;
}
long timeout;
BurnoutEntry next;
}
private BurnoutEntry burnout;
public RedstoneTorchPart()
{
}
public RedstoneTorchPart(int meta)
{
super(meta);
}
@Override
public Block getBlock()
{
return active() ? torchActive : torchIdle;
}
public boolean active()
{
return (meta&0x10) > 0;
}
@Override
public String getType()
{
return "mc_redtorch";
}
@Override
public int sideForMeta(int meta)
{
return super.sideForMeta(meta&7);
}
@Override
public Cuboid6 getBounds()
{
return getBounds(meta&7);
}
public static McBlockPart placement(World world, BlockCoord pos, int side)
{
if(side == 0)
return null;
pos = pos.copy().offset(side^1);
if(!world.isBlockSolidOnSide(pos.x, pos.y, pos.z, ForgeDirection.getOrientation(side)))
return null;
return new RedstoneTorchPart(sideMetaMap[side^1]|0x10);
}
@Override
public void randomDisplayTick(Random random)
{
if(!active())
return;
double d0 = x() + 0.5 + (random.nextFloat() - 0.5) * 0.2;
double d1 = y() + 0.7 + (random.nextFloat() - 0.5) * 0.2;
double d2 = z() + 0.5 + (random.nextFloat() - 0.5) * 0.2;
double d3 = 0.22D;
double d4 = 0.27D;
World world = world();
int m = meta&7;
if (m == 1)
world.spawnParticle("reddust", d0 - d4, d1 + d3, d2, 0, 0, 0);
else if (m == 2)
world.spawnParticle("reddust", d0 + d4, d1 + d3, d2, 0, 0, 0);
else if (m == 3)
world.spawnParticle("reddust", d0, d1 + d3, d2 - d4, 0, 0, 0);
else if (m == 4)
world.spawnParticle("reddust", d0, d1 + d3, d2 + d4, 0, 0, 0);
else
world.spawnParticle("reddust", d0, d1, d2, 0, 0, 0);
}
@Override
public ItemStack pickItem(MovingObjectPosition hit)
{
return new ItemStack(torchActive);
}
@Override
public void onNeighborChanged()
{
if(!world().isRemote)
{
if(!dropIfCantStay() && isBeingPowered() == active())
scheduleTick(2);
}
}
public boolean isBeingPowered()
{
int side = metaSideMap[meta&7];
return RedstoneInteractions.getPowerTo(this, side) > 0;
}
@Override
public void scheduledTick()
{
if(!world().isRemote && isBeingPowered() == active())
toggle();
}
@Override
public void randomUpdate()
{
scheduledTick();
}
private boolean burnedOut(boolean add)
{
long time = world().getTotalWorldTime();
while(burnout != null && burnout.timeout <= time)
burnout = burnout.next;
if(add)
{
BurnoutEntry e = new BurnoutEntry(world().getTotalWorldTime()+60);
if(burnout == null)
burnout = e;
else
{
BurnoutEntry b = burnout;
while(b.next != null)
b = b.next;
b.next = e;
}
}
if(burnout == null)
return false;
int i = 0;
BurnoutEntry b = burnout;
while(b != null)
{
i++;
b = b.next;
}
return i >= 8;
}
private void toggle()
{
if(active())//deactivating
{
if(burnedOut(true))
{
World world = world();
Random rand = world.rand;
world.playSoundEffect(x()+0.5, y()+0.5, z()+0.5, "random.fizz", 0.5F, 2.6F + (rand.nextFloat() - rand.nextFloat()) * 0.8F);
McMultipartSPH.spawnBurnoutSmoke(world, x(), y(), z());
}
}
else if(burnedOut(false))
{
return;
}
meta ^= 0x10;
sendDescUpdate();
tile().markDirty();
tile().notifyPartChange(this);
tile().notifyNeighborChange(1);
}
@Override
public void drop() {
meta|=0x10;//set state to on for drop
super.drop();
}
@Override
public void onRemoved()
{
if(active())
tile().notifyNeighborChange(1);
}
@Override
public void onAdded()
{
if(active())
tile().notifyNeighborChange(1);
onNeighborChanged();
}
@Override
public int strongPowerLevel(int side)
{
return side == 1 && active() ? 15 : 0;
}
@Override
public int weakPowerLevel(int side)
{
return active() && side != metaSideMap[meta&7] ? 15 : 0;
}
@Override
public boolean canConnectRedstone(int side)
{
return true;
}
@Override
public int getFace() {
return metaSideMap[meta&7];
}
}

View file

@ -0,0 +1,138 @@
package codechicken.multipart.minecraft;
import java.util.Random;
import net.minecraft.block.Block;
import net.minecraft.block.BlockTorch;
import net.minecraft.world.World;
import net.minecraftforge.common.ForgeDirection;
import codechicken.multipart.IRandomDisplayTick;
import codechicken.lib.vec.BlockCoord;
import codechicken.lib.vec.Cuboid6;
public class TorchPart extends McSidedMetaPart implements IRandomDisplayTick
{
public static BlockTorch torch = (BlockTorch) Block.torchWood;
public static int[] metaSideMap = new int[]{-1, 4, 5, 2, 3, 0};
public static int[] sideMetaMap = new int[]{5, 0, 3, 4, 1, 2};
public TorchPart()
{
}
public TorchPart(int meta)
{
super(meta);
}
@Override
public Block getBlock()
{
return torch;
}
@Override
public String getType()
{
return "mc_torch";
}
@Override
public Cuboid6 getBounds()
{
return getBounds(meta);
}
public Cuboid6 getBounds(int meta)
{
double d = 0.15;
if (meta == 1)
return new Cuboid6(0, 0.2, 0.5 - d, d * 2, 0.8, 0.5 + d);
if (meta == 2)
return new Cuboid6(1 - d * 2, 0.2, 0.5 - d, 1, 0.8, 0.5 + d);
if (meta == 3)
return new Cuboid6(0.5 - d, 0.2, 0, 0.5 + d, 0.8, d * 2);
if (meta == 4)
return new Cuboid6(0.5 - d, 0.2, 1 - d * 2, 0.5 + d, 0.8, 1);
d = 0.1;
return new Cuboid6(0.5 - d, 0, 0.5 - d, 0.5 + d, 0.6, 0.5 + d);
}
@Override
public int sideForMeta(int meta)
{
return metaSideMap[meta];
}
@Override
public boolean canStay()
{
if(sideForMeta(meta) == 0)
{
Block block = Block.blocksList[world().getBlockId(x(), y()-1, z())];
if(block != null && block.canPlaceTorchOnTop(world(), x(), y()-1, z()))
return true;
}
return super.canStay();
}
public static McBlockPart placement(World world, BlockCoord pos, int side)
{
if(side == 0)
return null;
pos = pos.copy().offset(side^1);
if(!world.isBlockSolidOnSide(pos.x, pos.y, pos.z, ForgeDirection.getOrientation(side)))
{
if(side == 1)
{
Block block = Block.blocksList[world.getBlockId(pos.x, pos.y, pos.z)];
if(block == null || !block.canPlaceTorchOnTop(world, pos.x, pos.y, pos.z))
return null;
}
else
{
return null;
}
}
return new TorchPart(sideMetaMap[side^1]);
}
@Override
public void randomDisplayTick(Random random)
{
double d0 = x() + 0.5;
double d1 = y() + 0.7;
double d2 = z() + 0.5;
double d3 = 0.22D;
double d4 = 0.27D;
World world = world();
if (meta == 1)
{
world.spawnParticle("smoke", d0 - d4, d1 + d3, d2, 0, 0, 0);
world.spawnParticle("flame", d0 - d4, d1 + d3, d2, 0, 0, 0);
}
else if (meta == 2)
{
world.spawnParticle("smoke", d0 + d4, d1 + d3, d2, 0, 0, 0);
world.spawnParticle("flame", d0 + d4, d1 + d3, d2, 0, 0, 0);
}
else if (meta == 3)
{
world.spawnParticle("smoke", d0, d1 + d3, d2 - d4, 0, 0, 0);
world.spawnParticle("flame", d0, d1 + d3, d2 - d4, 0, 0, 0);
}
else if (meta == 4)
{
world.spawnParticle("smoke", d0, d1 + d3, d2 + d4, 0, 0, 0);
world.spawnParticle("flame", d0, d1 + d3, d2 + d4, 0, 0, 0);
}
else
{
world.spawnParticle("smoke", d0, d1, d2, 0, 0, 0);
world.spawnParticle("flame", d0, d1, d2, 0, 0, 0);
}
}
}

View file

@ -0,0 +1,48 @@
package codechicken.multipart.nei;
import codechicken.lib.lang.LangUtil;
import codechicken.microblock.MicroblockClass;
import codechicken.microblock.MicroblockClassRegistry;
import codechicken.microblock.handler.MicroblockProxy;
import codechicken.nei.MultiItemRange;
import codechicken.nei.api.API;
import codechicken.nei.api.IConfigureNEI;
public class NEI_MicroblockConfig implements IConfigureNEI
{
@Override
public void loadConfig()
{
int microID = MicroblockProxy.itemMicro().itemID;
MicroblockClass[] microClasses = MicroblockClassRegistry.classes();
for(int c = 0; c < microClasses.length; c++)
{
MicroblockClass mcrClass = microClasses[c];
if(mcrClass == null)
continue;
addSubset(mcrClass, microID, c<<8|1);
addSubset(mcrClass, microID, c<<8|2);
addSubset(mcrClass, microID, c<<8|4);
}
}
private void addSubset(MicroblockClass mcrClass, int microID, int i)
{
API.addSetRange("Microblocks."+LangUtil.translateG(mcrClass.getName()+"."+(i&0xFF)+".subset"),
new MultiItemRange().add(microID, i, i));
}
@Override
public String getName()
{
return "ForgeMultipart";
}
@Override
public String getVersion()
{
return "1.0.0.0";
}
}

View file

@ -0,0 +1,129 @@
package codechicken.multipart.scalatraits
import scala.collection.mutable.ListBuffer
import net.minecraftforge.common.ForgeDirection
import codechicken.multipart.TMultiPart
import codechicken.multipart.TileMultipart
import net.minecraftforge.fluids.IFluidHandler
import net.minecraftforge.fluids.FluidStack
import net.minecraftforge.fluids.FluidTankInfo
import net.minecraftforge.fluids.Fluid
/**
* Mixin trait implementation for parts implementing IFluidHandler.
* Distributes fluid manipulation among fluid handling parts.
*/
trait TFluidHandlerTile extends TileMultipart with IFluidHandler
{
var tankList = ListBuffer[IFluidHandler]()
override def copyFrom(that:TileMultipart)
{
super.copyFrom(that)
if(that.isInstanceOf[TFluidHandlerTile])
tankList = that.asInstanceOf[TFluidHandlerTile].tankList
}
override def bindPart(part:TMultiPart)
{
super.bindPart(part)
if(part.isInstanceOf[IFluidHandler])
tankList+=part.asInstanceOf[IFluidHandler]
}
override def partRemoved(part:TMultiPart, p:Int)
{
super.partRemoved(part, p)
if(part.isInstanceOf[IFluidHandler])
tankList-=part.asInstanceOf[IFluidHandler]
}
override def clearParts()
{
super.clearParts()
tankList.clear()
}
override def getTankInfo(dir:ForgeDirection):Array[FluidTankInfo] =
{
var tankCount:Int = 0
tankList.foreach(t => tankCount += t.getTankInfo(dir).length)
val tanks = new Array[FluidTankInfo](tankCount)
var i = 0
tankList.foreach(p => p.getTankInfo(dir).foreach{t =>
tanks(i) = t
i+=1
})
return tanks
}
override def fill(dir:ForgeDirection, liquid:FluidStack, doFill:Boolean):Int =
{
var filled = 0
val initial = liquid.amount
tankList.foreach(p =>
filled+=p.fill(dir, copy(liquid, initial-filled), doFill)
)
return filled
}
override def canFill(dir:ForgeDirection, liquid:Fluid) = tankList.find(_.canFill(dir, liquid)).isDefined
override def canDrain(dir:ForgeDirection, liquid:Fluid) = tankList.find(_.canDrain(dir, liquid)).isDefined
private def copy(liquid:FluidStack, quantity:Int):FluidStack =
{
val copy = liquid.copy
copy.amount = quantity
return copy
}
override def drain(dir:ForgeDirection, amount:Int, doDrain:Boolean):FluidStack =
{
var drained:FluidStack = null
var d_amount = 0
tankList.foreach{p =>
val drain = amount-d_amount
val ret = p.drain(dir, drain, false)
if(ret != null && ret.amount > 0 && (drained == null || drained.isFluidEqual(ret)))
{
if(doDrain)
p.drain(dir, drain, true)
if(drained == null)
drained = ret
d_amount+=ret.amount
}
}
if(drained != null)
drained.amount = d_amount
return drained
}
override def drain(dir:ForgeDirection, fluid:FluidStack, doDrain:Boolean):FluidStack =
{
val amount = fluid.amount
var drained:FluidStack = null
var d_amount = 0
tankList.foreach{p =>
val drain = copy(fluid, amount-d_amount)
val ret = p.drain(dir, drain, false)
if(ret != null && ret.amount > 0 && (drained == null || drained.isFluidEqual(ret)))
{
if(doDrain)
p.drain(dir, drain, true)
if(drained == null)
drained = ret
d_amount+=ret.amount
}
}
if(drained != null)
drained.amount = d_amount
return drained
}
}

View file

@ -0,0 +1,34 @@
package codechicken.multipart.scalatraits
import codechicken.multipart.TileMultipart
import codechicken.multipart.TMultiPart
import codechicken.multipart.JPartialOcclusion
import codechicken.multipart.PartialOcclusionTest
/**
* Implementation for the partial occlusion test.
*/
class TPartialOcclusionTile extends TileMultipart
{
override def occlusionTest(parts:Seq[TMultiPart], npart:TMultiPart):Boolean =
{
if(npart.isInstanceOf[JPartialOcclusion] && !partialOcclusionTest(parts:+npart))
return false
return super.occlusionTest(parts, npart)
}
def partialOcclusionTest(parts:Seq[TMultiPart]):Boolean =
{
val test = new PartialOcclusionTest(parts.length)
var i = 0
while(i < parts.length)
{
val part = parts(i)
if(part.isInstanceOf[JPartialOcclusion])
test.fill(i, part.asInstanceOf[JPartialOcclusion])
i+=1
}
return test()
}
}

View file

@ -0,0 +1,17 @@
package codechicken.multipart.scalatraits
import codechicken.multipart.TileMultipartClient
import codechicken.multipart.IRandomDisplayTick
import java.util.Random
/**
* Saves processor time looping on tiles that don't need it
*/
trait TRandomDisplayTickTile extends TileMultipartClient
{
override def randomDisplayTick(random:Random)
{
for(p@(_p: IRandomDisplayTick) <- partList.iterator)
p.randomDisplayTick(random)
}
}

View file

@ -0,0 +1,87 @@
package codechicken.multipart.scalatraits
import codechicken.multipart.TileMultipart
import codechicken.multipart.IRedstonePart
import codechicken.multipart.RedstoneInteractions._
import codechicken.multipart.PartMap._
import codechicken.lib.vec.Rotation._
import codechicken.multipart.IRedstoneTile
import codechicken.multipart.TFacePart
import codechicken.multipart.TEdgePart
/**
* Mixin trait implementation for IRedstonePart
* Provides and overrides various redstone functions.
*/
trait TRedstoneTile extends TileMultipart with IRedstoneTile
{
/**
* Returns the strong (indirect) power being emitted fr
*/
override def strongPowerLevel(side:Int):Int =
{
var max = 0
for(p@(_p: IRedstonePart) <- partList.iterator)
{
val l = p.strongPowerLevel(side)
if(l > max) max = l
}
return max
}
def openConnections(side:Int):Int =
{
var m = 0x10
var i = 0
while(i < 4)
{
if(redstoneConductionE(edgeBetween(side, rotateSide(side&6, i))))
m|=1<<i
i+=1
}
m&=redstoneConductionF(side)
return m
}
def redstoneConductionF(i:Int) = partMap(i) match {
case null => 0x1F
case p => p.asInstanceOf[TFacePart].redstoneConductionMap
}
def redstoneConductionE(i:Int) = partMap(i) match {
case null => true
case p => p.asInstanceOf[TEdgePart].conductsRedstone
}
override def weakPowerLevel(side:Int):Int =
weakPowerLevel(side, otherConnectionMask(worldObj, xCoord, yCoord, zCoord, side, true))
override def canConnectRedstone(side:Int):Boolean =
{
val vside = vanillaToSide(side)
return (getConnectionMask(vside) & otherConnectionMask(worldObj, xCoord, yCoord, zCoord, vside, false)) > 0
}
def getConnectionMask(side:Int):Int =
{
val mask = openConnections(side)
var res = 0
partList.foreach(p =>
res|=connectionMask(p, side)&mask)
return res
}
def weakPowerLevel(side:Int, mask:Int):Int =
{
val tmask = openConnections(side)&mask
var max = 0
partList.foreach(p =>
if((connectionMask(p, side)&tmask) > 0)
{
val l = p.asInstanceOf[IRedstonePart].weakPowerLevel(side)
if(l > max) max = l
})
return max
}
}

View file

@ -0,0 +1,64 @@
package codechicken.multipart.scalatraits
import codechicken.multipart.TileMultipart
import codechicken.multipart.TMultiPart
import codechicken.multipart.TSlottedPart
/**
* Mixin implementation for TSlottedPart.
* Puts parts into a slot array for quick access at the cost of memory consumption
*/
trait TSlottedTile extends TileMultipart
{
var v_partMap = new Array[TMultiPart](27)
override def copyFrom(that:TileMultipart)
{
super.copyFrom(that)
if(that.isInstanceOf[TSlottedTile])
v_partMap = that.asInstanceOf[TSlottedTile].v_partMap
}
override def partMap(slot:Int) = v_partMap(slot)
override def clearParts()
{
super.clearParts()
for(i <- 0 until v_partMap.length)
v_partMap(i) = null
}
override def partRemoved(part:TMultiPart, p:Int)
{
super.partRemoved(part, p)
if(part.isInstanceOf[TSlottedPart])
for(i <- 0 until 27)
if(partMap(i) == part)
v_partMap(i) = null
}
override def canAddPart(part:TMultiPart):Boolean =
{
if(part.isInstanceOf[TSlottedPart])
{
val slotMask = part.asInstanceOf[TSlottedPart].getSlotMask
for(i <- 0 until v_partMap.length)
if((slotMask&1<<i) != 0 && partMap(i) != null)
return false
}
return super.canAddPart(part)
}
override def bindPart(part:TMultiPart)
{
super.bindPart(part)
if(part.isInstanceOf[TSlottedPart])
{
val mask = part.asInstanceOf[TSlottedPart].getSlotMask
for(i <- 0 until 27)
if ((mask&1<<i) > 0)
v_partMap(i) = part
}
}
}

View file

@ -0,0 +1,60 @@
package codechicken.multipart.scalatraits
import codechicken.multipart.TMultiPart
import codechicken.multipart.INeighborTileChange
import codechicken.multipart.TileMultipart
import codechicken.lib.vec.BlockCoord
/**
* Mixin implementation for INeighborTileChange
*
* Reduces unnecessary computation
*/
trait TTileChangeTile extends TileMultipart {
var weakTileChanges = false
override def copyFrom(that:TileMultipart)
{
super.copyFrom(that)
if(that.isInstanceOf[TTileChangeTile])
weakTileChanges = that.asInstanceOf[TTileChangeTile].weakTileChanges
}
override def bindPart(part:TMultiPart)
{
super.bindPart(part)
if(part.isInstanceOf[INeighborTileChange])
weakTileChanges|=part.asInstanceOf[INeighborTileChange].weakTileChanges
}
override def clearParts()
{
super.clearParts()
weakTileChanges = false
}
override def partRemoved(part:TMultiPart, p:Int) {
super.partRemoved(part, p)
weakTileChanges = partList.exists(p => p.isInstanceOf[INeighborTileChange] && p.asInstanceOf[INeighborTileChange].weakTileChanges)
}
override def onNeighborTileChange(tileX:Int, tileY:Int, tileZ:Int)
{
super.onNeighborTileChange(tileX, tileY, tileZ)
val offset = new BlockCoord(tileX, tileY, tileZ).sub(xCoord, yCoord, zCoord)
val diff = offset.absSum
val side = offset.toSide
if(side < 0 || diff <= 0 || diff > 2)
return
val weak = diff == 2
if(weak && !weakTileChanges)
return
operate{ p =>
if(p.isInstanceOf[INeighborTileChange])
p.asInstanceOf[INeighborTileChange].onNeighborTileChanged(side, weak)
}
}
}

View file

@ -94,4 +94,10 @@ public class CommandMekanism extends CommandBase
}
}
}
@Override
public int compareTo(Object obj)
{
return 0;
}
}