From 4f68913eba49905dadf54da9ad9b72e7e64fe90d Mon Sep 17 00:00:00 2001 From: Aidan Brady Date: Sat, 4 Jan 2014 13:16:24 -0500 Subject: [PATCH] Maybe fix build? Also temporarily adding FMP lib --- .../multipart/BlockMultipart.scala | 311 ++++++++ .../multipart/ControlKeyModifier.scala | 71 ++ common/codechicken/multipart/IDWriter.scala | 33 + .../multipart/INeighborTileChange.scala | 15 + .../multipart/IRandomDisplayTick.scala | 16 + .../multipart/IRandomUpdateTick.scala | 12 + .../codechicken/multipart/IRedstonePart.scala | 243 ++++++ .../codechicken/multipart/ItemMultiPart.scala | 54 ++ .../multipart/MultiPartRegistry.scala | 188 +++++ .../multipart/MultipartGenerator.scala | 219 ++++++ .../multipart/MultipartHelper.scala | 69 ++ .../multipart/MultipartRenderer.scala | 78 ++ .../multipart/PacketScheduler.scala | 82 ++ common/codechicken/multipart/PartMap.java | 134 ++++ .../codechicken/multipart/TCuboidPart.scala | 37 + common/codechicken/multipart/TEdgePart.scala | 12 + common/codechicken/multipart/TFacePart.scala | 16 + .../multipart/TIconHitEffects.scala | 81 ++ common/codechicken/multipart/TMultiPart.scala | 295 ++++++++ .../multipart/TNormalOcclusion.scala | 68 ++ .../multipart/TPartialOcclusion.scala | 76 ++ .../codechicken/multipart/TSlottedPart.scala | 15 + .../codechicken/multipart/TickScheduler.scala | 281 +++++++ .../codechicken/multipart/TileMultipart.scala | 699 ++++++++++++++++++ .../multipart/asm/ASMMixinCompiler.scala | 623 ++++++++++++++++ .../multipart/asm/ASMMixinFactory.scala | 311 ++++++++ .../multipart/asm/ByteCodecs.scala | 213 ++++++ .../multipart/asm/IMultipartFactory.scala | 12 + .../multipart/asm/ScalaSignature.scala | 341 +++++++++ .../multipart/asm/StackAnalyser.scala | 314 ++++++++ .../handler/MultipartEventHandler.scala | 87 +++ .../multipart/handler/MultipartMod.scala | 45 ++ .../multipart/handler/MultipartSaveLoad.scala | 76 ++ .../multipart/handler/packethandlers.scala | 174 +++++ .../multipart/handler/proxies.scala | 100 +++ .../multipart/minecraft/ButtonPart.java | 183 +++++ .../multipart/minecraft/Content.java | 66 ++ .../multipart/minecraft/EventHandler.java | 118 +++ .../multipart/minecraft/IPartMeta.java | 15 + .../multipart/minecraft/LeverPart.java | 151 ++++ .../multipart/minecraft/McBlockPart.java | 92 +++ .../multipart/minecraft/McMetaPart.java | 85 +++ .../multipart/minecraft/McMultipartCPH.java | 33 + .../multipart/minecraft/McMultipartSPH.java | 28 + .../multipart/minecraft/McSidedMetaPart.java | 69 ++ .../minecraft/MinecraftMultipartMod.java | 29 + .../multipart/minecraft/PartMetaAccess.java | 141 ++++ .../minecraft/RedstoneTorchPart.java | 245 ++++++ .../multipart/minecraft/TorchPart.java | 138 ++++ .../multipart/nei/NEI_MicroblockConfig.java | 48 ++ .../scalatraits/TFluidHandlerTile.scala | 129 ++++ .../scalatraits/TPartialOcclusionTile.scala | 34 + .../scalatraits/TRandomDisplayTickTile.scala | 17 + .../multipart/scalatraits/TRedstoneTile.scala | 87 +++ .../multipart/scalatraits/TSlottedTile.scala | 64 ++ .../scalatraits/TTileChangeTile.scala | 60 ++ common/mekanism/common/CommandMekanism.java | 6 + 57 files changed, 7239 insertions(+) create mode 100644 common/codechicken/multipart/BlockMultipart.scala create mode 100644 common/codechicken/multipart/ControlKeyModifier.scala create mode 100644 common/codechicken/multipart/IDWriter.scala create mode 100644 common/codechicken/multipart/INeighborTileChange.scala create mode 100644 common/codechicken/multipart/IRandomDisplayTick.scala create mode 100644 common/codechicken/multipart/IRandomUpdateTick.scala create mode 100644 common/codechicken/multipart/IRedstonePart.scala create mode 100644 common/codechicken/multipart/ItemMultiPart.scala create mode 100644 common/codechicken/multipart/MultiPartRegistry.scala create mode 100644 common/codechicken/multipart/MultipartGenerator.scala create mode 100644 common/codechicken/multipart/MultipartHelper.scala create mode 100644 common/codechicken/multipart/MultipartRenderer.scala create mode 100644 common/codechicken/multipart/PacketScheduler.scala create mode 100644 common/codechicken/multipart/PartMap.java create mode 100644 common/codechicken/multipart/TCuboidPart.scala create mode 100644 common/codechicken/multipart/TEdgePart.scala create mode 100644 common/codechicken/multipart/TFacePart.scala create mode 100644 common/codechicken/multipart/TIconHitEffects.scala create mode 100644 common/codechicken/multipart/TMultiPart.scala create mode 100644 common/codechicken/multipart/TNormalOcclusion.scala create mode 100644 common/codechicken/multipart/TPartialOcclusion.scala create mode 100644 common/codechicken/multipart/TSlottedPart.scala create mode 100644 common/codechicken/multipart/TickScheduler.scala create mode 100644 common/codechicken/multipart/TileMultipart.scala create mode 100644 common/codechicken/multipart/asm/ASMMixinCompiler.scala create mode 100644 common/codechicken/multipart/asm/ASMMixinFactory.scala create mode 100644 common/codechicken/multipart/asm/ByteCodecs.scala create mode 100644 common/codechicken/multipart/asm/IMultipartFactory.scala create mode 100644 common/codechicken/multipart/asm/ScalaSignature.scala create mode 100644 common/codechicken/multipart/asm/StackAnalyser.scala create mode 100644 common/codechicken/multipart/handler/MultipartEventHandler.scala create mode 100644 common/codechicken/multipart/handler/MultipartMod.scala create mode 100644 common/codechicken/multipart/handler/MultipartSaveLoad.scala create mode 100644 common/codechicken/multipart/handler/packethandlers.scala create mode 100644 common/codechicken/multipart/handler/proxies.scala create mode 100644 common/codechicken/multipart/minecraft/ButtonPart.java create mode 100644 common/codechicken/multipart/minecraft/Content.java create mode 100644 common/codechicken/multipart/minecraft/EventHandler.java create mode 100644 common/codechicken/multipart/minecraft/IPartMeta.java create mode 100644 common/codechicken/multipart/minecraft/LeverPart.java create mode 100644 common/codechicken/multipart/minecraft/McBlockPart.java create mode 100644 common/codechicken/multipart/minecraft/McMetaPart.java create mode 100644 common/codechicken/multipart/minecraft/McMultipartCPH.java create mode 100644 common/codechicken/multipart/minecraft/McMultipartSPH.java create mode 100644 common/codechicken/multipart/minecraft/McSidedMetaPart.java create mode 100644 common/codechicken/multipart/minecraft/MinecraftMultipartMod.java create mode 100644 common/codechicken/multipart/minecraft/PartMetaAccess.java create mode 100644 common/codechicken/multipart/minecraft/RedstoneTorchPart.java create mode 100644 common/codechicken/multipart/minecraft/TorchPart.java create mode 100644 common/codechicken/multipart/nei/NEI_MicroblockConfig.java create mode 100644 common/codechicken/multipart/scalatraits/TFluidHandlerTile.scala create mode 100644 common/codechicken/multipart/scalatraits/TPartialOcclusionTile.scala create mode 100644 common/codechicken/multipart/scalatraits/TRandomDisplayTickTile.scala create mode 100644 common/codechicken/multipart/scalatraits/TRedstoneTile.scala create mode 100644 common/codechicken/multipart/scalatraits/TSlottedTile.scala create mode 100644 common/codechicken/multipart/scalatraits/TTileChangeTile.scala diff --git a/common/codechicken/multipart/BlockMultipart.scala b/common/codechicken/multipart/BlockMultipart.scala new file mode 100644 index 000000000..baa7ec501 --- /dev/null +++ b/common/codechicken/multipart/BlockMultipart.scala @@ -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 +} \ No newline at end of file diff --git a/common/codechicken/multipart/ControlKeyModifier.scala b/common/codechicken/multipart/ControlKeyModifier.scala new file mode 100644 index 000000000..fb70bcb55 --- /dev/null +++ b/common/codechicken/multipart/ControlKeyModifier.scala @@ -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) +} \ No newline at end of file diff --git a/common/codechicken/multipart/IDWriter.scala b/common/codechicken/multipart/IDWriter.scala new file mode 100644 index 000000000..9a240dfbf --- /dev/null +++ b/common/codechicken/multipart/IDWriter.scala @@ -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() + } + } +} \ No newline at end of file diff --git a/common/codechicken/multipart/INeighborTileChange.scala b/common/codechicken/multipart/INeighborTileChange.scala new file mode 100644 index 000000000..6a8eb11a4 --- /dev/null +++ b/common/codechicken/multipart/INeighborTileChange.scala @@ -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) +} \ No newline at end of file diff --git a/common/codechicken/multipart/IRandomDisplayTick.scala b/common/codechicken/multipart/IRandomDisplayTick.scala new file mode 100644 index 000000000..a51245deb --- /dev/null +++ b/common/codechicken/multipart/IRandomDisplayTick.scala @@ -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) +} \ No newline at end of file diff --git a/common/codechicken/multipart/IRandomUpdateTick.scala b/common/codechicken/multipart/IRandomUpdateTick.scala new file mode 100644 index 000000000..f689dceca --- /dev/null +++ b/common/codechicken/multipart/IRandomUpdateTick.scala @@ -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() +} \ No newline at end of file diff --git a/common/codechicken/multipart/IRedstonePart.scala b/common/codechicken/multipart/IRedstonePart.scala new file mode 100644 index 000000000..171fb0fa2 --- /dev/null +++ b/common/codechicken/multipart/IRedstonePart.scala @@ -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<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) +} diff --git a/common/codechicken/multipart/MultipartGenerator.scala b/common/codechicken/multipart/MultipartGenerator.scala new file mode 100644 index 000000000..e48c82db6 --- /dev/null +++ b/common/codechicken/multipart/MultipartGenerator.scala @@ -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) + } +} \ No newline at end of file diff --git a/common/codechicken/multipart/MultipartHelper.scala b/common/codechicken/multipart/MultipartHelper.scala new file mode 100644 index 000000000..47fdc418c --- /dev/null +++ b/common/codechicken/multipart/MultipartHelper.scala @@ -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 + *
+ * 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. + *
+ * 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. + *
+ * 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) + } + } +} \ No newline at end of file diff --git a/common/codechicken/multipart/MultipartRenderer.scala b/common/codechicken/multipart/MultipartRenderer.scala new file mode 100644 index 000000000..c5251e475 --- /dev/null +++ b/common/codechicken/multipart/MultipartRenderer.scala @@ -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 +} diff --git a/common/codechicken/multipart/PacketScheduler.scala b/common/codechicken/multipart/PacketScheduler.scala new file mode 100644 index 000000000..d67ecc19e --- /dev/null +++ b/common/codechicken/multipart/PacketScheduler.scala @@ -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){} +} \ No newline at end of file diff --git a/common/codechicken/multipart/PartMap.java b/common/codechicken/multipart/PartMap.java new file mode 100644 index 000000000..9100ec9c0 --- /dev/null +++ b/common/codechicken/multipart/PartMap.java @@ -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<>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]; + } +} diff --git a/common/codechicken/multipart/TCuboidPart.scala b/common/codechicken/multipart/TCuboidPart.scala new file mode 100644 index 000000000..bab44f60d --- /dev/null +++ b/common/codechicken/multipart/TCuboidPart.scala @@ -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) + } +} \ No newline at end of file diff --git a/common/codechicken/multipart/TEdgePart.scala b/common/codechicken/multipart/TEdgePart.scala new file mode 100644 index 000000000..c8e8e8832 --- /dev/null +++ b/common/codechicken/multipart/TEdgePart.scala @@ -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 +} \ No newline at end of file diff --git a/common/codechicken/multipart/TFacePart.scala b/common/codechicken/multipart/TFacePart.scala new file mode 100644 index 000000000..bb97b1a27 --- /dev/null +++ b/common/codechicken/multipart/TFacePart.scala @@ -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 +} \ No newline at end of file diff --git a/common/codechicken/multipart/TIconHitEffects.scala b/common/codechicken/multipart/TIconHitEffects.scala new file mode 100644 index 000000000..ceec6337b --- /dev/null +++ b/common/codechicken/multipart/TIconHitEffects.scala @@ -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) + } +} \ No newline at end of file diff --git a/common/codechicken/multipart/TMultiPart.scala b/common/codechicken/multipart/TMultiPart.scala new file mode 100644 index 000000000..1d9a02a30 --- /dev/null +++ b/common/codechicken/multipart/TMultiPart.scala @@ -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){} +} \ No newline at end of file diff --git a/common/codechicken/multipart/TNormalOcclusion.scala b/common/codechicken/multipart/TNormalOcclusion.scala new file mode 100644 index 000000000..a6b2e5a0c --- /dev/null +++ b/common/codechicken/multipart/TNormalOcclusion.scala @@ -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 +} \ No newline at end of file diff --git a/common/codechicken/multipart/TPartialOcclusion.scala b/common/codechicken/multipart/TPartialOcclusion.scala new file mode 100644 index 000000000..c0b1c79dd --- /dev/null +++ b/common/codechicken/multipart/TPartialOcclusion.scala @@ -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 +} \ No newline at end of file diff --git a/common/codechicken/multipart/TSlottedPart.scala b/common/codechicken/multipart/TSlottedPart.scala new file mode 100644 index 000000000..c6e75a924 --- /dev/null +++ b/common/codechicken/multipart/TSlottedPart.scala @@ -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<>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 +} \ No newline at end of file diff --git a/common/codechicken/multipart/TileMultipart.scala b/common/codechicken/multipart/TileMultipart.scala new file mode 100644 index 000000000..a1af03d35 --- /dev/null +++ b/common/codechicken/multipart/TileMultipart.scala @@ -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) + } +} diff --git a/common/codechicken/multipart/asm/ASMMixinCompiler.scala b/common/codechicken/multipart/asm/ASMMixinCompiler.scala new file mode 100644 index 000000000..b5017ca36 --- /dev/null +++ b/common/codechicken/multipart/asm/ASMMixinCompiler.scala @@ -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, "", "()V", null, null) + minit.visitVarInsn(ALOAD, 0) + minit.visitMethodInsn(INVOKESPECIAL, cnode.superName, "", "()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("")) + throw new IllegalArgumentException("Static initialisers are not permitted "+cnode.name+" as a multipart trait") + + if(mnode.name.equals("")) + { + 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("") && + 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)) + } + + +} \ No newline at end of file diff --git a/common/codechicken/multipart/asm/ASMMixinFactory.scala b/common/codechicken/multipart/asm/ASMMixinFactory.scala new file mode 100644 index 000000000..8f6c3a6cd --- /dev/null +++ b/common/codechicken/multipart/asm/ASMMixinFactory.scala @@ -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, "", "()V", null, null) + mv.visitCode() + mv.visitVarInsn(ALOAD, 0) + mv.visitMethodInsn(INVOKESPECIAL, superName, "", "()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, "", "()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, "", "()V", null, null) + mv.visitCode() + mv.visitVarInsn(ALOAD, 0) + mv.visitMethodInsn(INVOKESPECIAL, "codechicken/multipart/TileMultipart", "", "()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 +} \ No newline at end of file diff --git a/common/codechicken/multipart/asm/ByteCodecs.scala b/common/codechicken/multipart/asm/ByteCodecs.scala new file mode 100644 index 000000000..d52c4a403 --- /dev/null +++ b/common/codechicken/multipart/asm/ByteCodecs.scala @@ -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) + } +} \ No newline at end of file diff --git a/common/codechicken/multipart/asm/IMultipartFactory.scala b/common/codechicken/multipart/asm/IMultipartFactory.scala new file mode 100644 index 000000000..11a0c3c5d --- /dev/null +++ b/common/codechicken/multipart/asm/IMultipartFactory.scala @@ -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 +} \ No newline at end of file diff --git a/common/codechicken/multipart/asm/ScalaSignature.scala b/common/codechicken/multipart/asm/ScalaSignature.scala new file mode 100644 index 000000000..708a1eb41 --- /dev/null +++ b/common/codechicken/multipart/asm/ScalaSignature.scala @@ -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 = "" + 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 = "" + } + + 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) + } +} \ No newline at end of file diff --git a/common/codechicken/multipart/asm/StackAnalyser.scala b/common/codechicken/multipart/asm/StackAnalyser.scala new file mode 100644 index 000000000..444e778a9 --- /dev/null +++ b/common/codechicken/multipart/asm/StackAnalyser.scala @@ -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 _ => + } + } +} \ No newline at end of file diff --git a/common/codechicken/multipart/handler/MultipartEventHandler.scala b/common/codechicken/multipart/handler/MultipartEventHandler.scala new file mode 100644 index 000000000..93cdba7a2 --- /dev/null +++ b/common/codechicken/multipart/handler/MultipartEventHandler.scala @@ -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) + } + } +} \ No newline at end of file diff --git a/common/codechicken/multipart/handler/MultipartMod.scala b/common/codechicken/multipart/handler/MultipartMod.scala new file mode 100644 index 000000000..50e0040d9 --- /dev/null +++ b/common/codechicken/multipart/handler/MultipartMod.scala @@ -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() + } +} \ No newline at end of file diff --git a/common/codechicken/multipart/handler/MultipartSaveLoad.scala b/common/codechicken/multipart/handler/MultipartSaveLoad.scala new file mode 100644 index 000000000..fbac81235 --- /dev/null +++ b/common/codechicken/multipart/handler/MultipartSaveLoad.scala @@ -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() + } + } + } +} \ No newline at end of file diff --git a/common/codechicken/multipart/handler/packethandlers.scala b/common/codechicken/multipart/handler/packethandlers.scala new file mode 100644 index 000000000..853ba18a7 --- /dev/null +++ b/common/codechicken/multipart/handler/packethandlers.scala @@ -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 + } +} diff --git a/common/codechicken/multipart/handler/proxies.scala b/common/codechicken/multipart/handler/proxies.scala new file mode 100644 index 000000000..6087595dc --- /dev/null +++ b/common/codechicken/multipart/handler/proxies.scala @@ -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 +} \ No newline at end of file diff --git a/common/codechicken/multipart/minecraft/ButtonPart.java b/common/codechicken/multipart/minecraft/ButtonPart.java new file mode 100644 index 000000000..b08cb6542 --- /dev/null +++ b/common/codechicken/multipart/minecraft/ButtonPart.java @@ -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]; + } +} diff --git a/common/codechicken/multipart/minecraft/Content.java b/common/codechicken/multipart/minecraft/Content.java new file mode 100644 index 000000000..23b174751 --- /dev/null +++ b/common/codechicken/multipart/minecraft/Content.java @@ -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; + } +} diff --git a/common/codechicken/multipart/minecraft/EventHandler.java b/common/codechicken/multipart/minecraft/EventHandler.java new file mode 100644 index 000000000..d1f355df1 --- /dev/null +++ b/common/codechicken/multipart/minecraft/EventHandler.java @@ -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 placing = new ThreadLocal(); + + @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; + } +} diff --git a/common/codechicken/multipart/minecraft/IPartMeta.java b/common/codechicken/multipart/minecraft/IPartMeta.java new file mode 100644 index 000000000..a4c2cb4e0 --- /dev/null +++ b/common/codechicken/multipart/minecraft/IPartMeta.java @@ -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(); +} \ No newline at end of file diff --git a/common/codechicken/multipart/minecraft/LeverPart.java b/common/codechicken/multipart/minecraft/LeverPart.java new file mode 100644 index 000000000..981099c11 --- /dev/null +++ b/common/codechicken/multipart/minecraft/LeverPart.java @@ -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]; + } +} diff --git a/common/codechicken/multipart/minecraft/McBlockPart.java b/common/codechicken/multipart/minecraft/McBlockPart.java new file mode 100644 index 000000000..17ded3d54 --- /dev/null +++ b/common/codechicken/multipart/minecraft/McBlockPart.java @@ -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 getOcclusionBoxes() + { + return Arrays.asList(getBounds()); + } + + @Override + public Iterable getCollisionBoxes() + { + return Collections.emptyList(); + } + + @Override + public Iterable 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]; + } +} diff --git a/common/codechicken/multipart/minecraft/McMetaPart.java b/common/codechicken/multipart/minecraft/McMetaPart.java new file mode 100644 index 000000000..ba02e3fe9 --- /dev/null +++ b/common/codechicken/multipart/minecraft/McMetaPart.java @@ -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()); + } +} diff --git a/common/codechicken/multipart/minecraft/McMultipartCPH.java b/common/codechicken/multipart/minecraft/McMultipartCPH.java new file mode 100644 index 000000000..42b57362f --- /dev/null +++ b/common/codechicken/multipart/minecraft/McMultipartCPH.java @@ -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); + } +} diff --git a/common/codechicken/multipart/minecraft/McMultipartSPH.java b/common/codechicken/multipart/minecraft/McMultipartSPH.java new file mode 100644 index 000000000..85222c620 --- /dev/null +++ b/common/codechicken/multipart/minecraft/McMultipartSPH.java @@ -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); + } +} diff --git a/common/codechicken/multipart/minecraft/McSidedMetaPart.java b/common/codechicken/multipart/minecraft/McSidedMetaPart.java new file mode 100644 index 000000000..81eb461a7 --- /dev/null +++ b/common/codechicken/multipart/minecraft/McSidedMetaPart.java @@ -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< 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]; + } +} diff --git a/common/codechicken/multipart/minecraft/TorchPart.java b/common/codechicken/multipart/minecraft/TorchPart.java new file mode 100644 index 000000000..ea8822b82 --- /dev/null +++ b/common/codechicken/multipart/minecraft/TorchPart.java @@ -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); + } + } +} diff --git a/common/codechicken/multipart/nei/NEI_MicroblockConfig.java b/common/codechicken/multipart/nei/NEI_MicroblockConfig.java new file mode 100644 index 000000000..1f5f00ee2 --- /dev/null +++ b/common/codechicken/multipart/nei/NEI_MicroblockConfig.java @@ -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"; + } +} diff --git a/common/codechicken/multipart/scalatraits/TFluidHandlerTile.scala b/common/codechicken/multipart/scalatraits/TFluidHandlerTile.scala new file mode 100644 index 000000000..2d49b37f2 --- /dev/null +++ b/common/codechicken/multipart/scalatraits/TFluidHandlerTile.scala @@ -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 + } +} \ No newline at end of file diff --git a/common/codechicken/multipart/scalatraits/TPartialOcclusionTile.scala b/common/codechicken/multipart/scalatraits/TPartialOcclusionTile.scala new file mode 100644 index 000000000..7deb96865 --- /dev/null +++ b/common/codechicken/multipart/scalatraits/TPartialOcclusionTile.scala @@ -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() + } +} \ No newline at end of file diff --git a/common/codechicken/multipart/scalatraits/TRandomDisplayTickTile.scala b/common/codechicken/multipart/scalatraits/TRandomDisplayTickTile.scala new file mode 100644 index 000000000..0759a66a7 --- /dev/null +++ b/common/codechicken/multipart/scalatraits/TRandomDisplayTickTile.scala @@ -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) + } +} \ No newline at end of file diff --git a/common/codechicken/multipart/scalatraits/TRedstoneTile.scala b/common/codechicken/multipart/scalatraits/TRedstoneTile.scala new file mode 100644 index 000000000..db619e679 --- /dev/null +++ b/common/codechicken/multipart/scalatraits/TRedstoneTile.scala @@ -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< 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 + } +} + diff --git a/common/codechicken/multipart/scalatraits/TSlottedTile.scala b/common/codechicken/multipart/scalatraits/TSlottedTile.scala new file mode 100644 index 000000000..2c1ca638b --- /dev/null +++ b/common/codechicken/multipart/scalatraits/TSlottedTile.scala @@ -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< 0) + v_partMap(i) = part + } + } +} \ No newline at end of file diff --git a/common/codechicken/multipart/scalatraits/TTileChangeTile.scala b/common/codechicken/multipart/scalatraits/TTileChangeTile.scala new file mode 100644 index 000000000..76a93ea7b --- /dev/null +++ b/common/codechicken/multipart/scalatraits/TTileChangeTile.scala @@ -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) + } + } +} \ No newline at end of file diff --git a/common/mekanism/common/CommandMekanism.java b/common/mekanism/common/CommandMekanism.java index f5fafca4c..2eb0c8cd8 100644 --- a/common/mekanism/common/CommandMekanism.java +++ b/common/mekanism/common/CommandMekanism.java @@ -94,4 +94,10 @@ public class CommandMekanism extends CommandBase } } } + + @Override + public int compareTo(Object obj) + { + return 0; + } }