ChickenBones Multipart is an API / Library by Chickenbones to create multiple Tileentitys inside a single block, so called Parts.
Note:
The mod is released under the name 'Forge' Multipart, however, it is NOT official and has no relation to the official Forge project. As such we will refer to it as CBMP in reference to it's author ChickenBones.
It also comes with a submod called 'Forge' Microblock (Again, no relation to Forge itself) which adds some parts to, for instance, cover up pipes. Some spotlights can be found on its official Minecraft Forums Topic. [Click Here]
This post will contain all CBMP related tutorials. If you want to make your own just post it as a reply and I am going to add it.
CBMP makes use of a programming language called Scala. Please make sure that you have basic knowledge of Scala since almost all of CBMP's code is written in scala.
On of the best tutorials for Scala is this one:
There's also a Java implementation of this tutorial by larsgerrits
https://github.com/Larsg310/FMP-Tutorial
Another great tutorial was made by Whov:
http://whov.altervista.org/forge-modding-tutorials/
All Tutorials in chronological order:
[spoiler=Installing Scala]
[*]Download and Install the latest Scala binaries from: http://www.scala-lang.org/download/
[*]IntelliJ Idea is the better choice because it suggests scala code.
Eclipse: (Please note that the scala plugin is not ready for Eclipse Luna yet)
[*]IntelliJ Idea:
[*]Go to: File / Settings / Plugins and click 'Install JetBrains Plugin...'
[spoiler=Image]
[*]Choose "Scala" from the List and Install the plugin.
[*]Right-click on your project and choose 'Add FrameWork support' from the context menu.
[*]Select Scala in the list and correct the path for the scala home to where you installed your scala binaries. For me its C://Program File(x86)
[*]You should now be able to use scala code and files.
[spoiler=Adding CBMP to your enviroment]
[*]Create a /libs folder in your modding enviroment. (Where the build.gradle file is)
[*]Download the CBMP dev and src .jar from http://files.minecraftforge.net/ForgeMultipart/ and put them into the /libs folder.
[*]Download the CodeChickenCore and NEI dev and src .jar form http://chickenbones.net/Pages/links.html and put them into the /libs folder.
[*]Download the CodeChickenLib dev and src .jar from http://files.minecraftforge.net/CodeChickenLib/ and put them into the /libs folder.
[*]You might want to copy the CodeChickenCore and NEI dev.jar into /eclipse/mods because they don't seem to be compiled in 1.7.10 (Check this yourself)
[*]Finally link the dev.jar's as Librarys in your Enviroment and attach the sources. (You can simply Import both the same way in almost all IDE's and they will do the rest.)
You should now be able to use CBMP and NEI. You can test CBMP by placing two torches in a single block.
[spoiler=Creating your first part]
It's finally time: After some installation horror we can begin with our first part. This part is just going to be a hitbox half the size of a block in the middle of it but we will later make a pipe out of this.
You will also be able to place covers on its sides.
For part registration and functionality you will need 3 Basic classes.
The PartFactory which handles the registration itself, the PartItem you have in your inventory and the Actual part itself.
Lets begin with the Part factory: Right-click on the folder you want to have the class in and choose a new scala class / file (not a java class like you normally do) and call it whatever you want... I called mine PartFactory.
package com.example.examplemod
import codechicken.multipart.MultiPartRegistry
import codechicken.multipart.MultiPartRegistry.IPartFactory
//This is the actual registration class
class PartFactory extends IPartFactory {
//We call this method from our main mod class.
def init() {
//Parts are registered by using String maps. Each individual part / subtileEntity has a string like when you register a block.
MultiPartRegistry.registerParts(this, Array[string](
"examplePipe"
))
}
//This method is overridden from IPartFactory and returns a new Part.class matching the assigned string name.
override def createPart(name: String, client: Boolean) = name match {
case "examplePipe" => new ExamplePipe
case _ => null
}
}
//This is the static object used to call the Reg. class
object PartRegistry extends PartFactory
Next we want to take a look at the PartItem, which is basically a normal item implementing an Interface which makes it place a part in the world when you right-click with it.
We, again, create a new scala file and call it Items or PartItems because we can use it later for multiple PartItem classes.
package com.example.examplemod
import codechicken.lib.vec.{Vector3, BlockCoord}
import codechicken.multipart.{TItemMultiPart, MultiPartRegistry}
import net.minecraft.entity.player.EntityPlayer
import net.minecraft.item.{ItemStack, Item}
import net.minecraft.world.World
//We can add multiple classes like this one in this scala file.
class ItemPartExamplePipe extends Item with TItemMultiPart {
def newPart(item:ItemStack, player:EntityPlayer, world:World, pos:BlockCoord, side:Int, vhit:Vector3) = {
//We create a new part.class and return it. Any modifications (set NBT data in the part by Item NBT) can be made here.
val w = MultiPartRegistry.createPart("examplePipe", false).asInstanceOf[ExamplePipe]
w
}
}
Don't forget to register this item like any other item in your mod class.
Last but not least we want to create the Part itself. It is a bit like the combination of a block and a TileEntity and has all methods for Rendering, Hit- / Boundingboxes, Updates, NBT data and many more.
package com.example.examplemod
import codechicken.lib.raytracer.IndexedCuboid6
import codechicken.lib.vec.Cuboid6
import codechicken.multipart.{TMultiPart, TNormalOcclusion}
import scala.collection.JavaConverters._
class ExamplePipe extends TMultiPart with TNormalOcclusion {
//We have to return the pipes register name a last time.
override def getType = "examplePipe"
//Here we want to generate whatever boxes the Pipe is going to use later. For now it is only a center box but later we want to add sides for every possible connection.
def generateBoxes = {
import com.example.examplemod.PipeBoxes._
var boxes = Seq(oBounds(0))
boxes
}
//Here we return the collision boxes generated in generateBoxes(). We need to convert it to java because this method expects a java iterable not the scala one.
def generateCollisionBoxes = {
import mlb.technica.experience.PipeBoxes._
var boxes = Seq(oBounds(0))
boxes
}
//This method returns any occlusion boxes for the part to check if another part can be placed in the same space as this one.
override def getOcclusionBoxes = {
import mlb.technica.experience.PipeBoxes._
Seq(oBounds(0)).asJava
}
//This method is used to return any sub parts that can be broken or clicked by the player to render their outlines. The connections the pipe makes to its sides are such subparts.
override def getSubParts = {
val b = generateBoxes
var i = Seq[indexedCuboid6]()
for (c <- b) i :+= new IndexedCuboid6(0, c)
i.asJava
}
}
//This Object stores all the possible boxes.
object PipeBoxes {
var oBounds = {
val boxes = new Array[Cuboid6](1)
val w = 2/8D
boxes(0) = new Cuboid6(0.5-w, 0.5-w, 0.5-w, 0.5+w, 0.5+w, 0.5+w)
boxes
}
}
Finally we want to take a look at our examplemod class where everything should be registered.
package com.example.examplemod;
import net.minecraft.init.Blocks;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.Mod.EventHandler;
import cpw.mods.fml.common.event.FMLInitializationEvent;
@Mod(modid = ExampleMod.MODID, version = ExampleMod.VERSION)
public class ExampleMod
{
public static final String MODID = "examplemod";
public static final String VERSION = "1.0";
public static Item item_ExamplePipe;
@EventHandler
public void init(FMLInitializationEvent event) {
item_ExamplePipe = new ItemPartExamplePipe().setUnlocalizedName("ExamplePipe").setCreativeTab(someTab);
PartRegistry.init();
GameRegistry.registerItem(item_ExamplePipe, "ExamplePipe", MODID);
}
}
You should now have a part that does nothing at all...
Please make sure that there are not any missing Imports I might have forgotten.
[spoiler=Using multiple Hit / Bounding-boxes]
Having multiple Hit and Bounding-boxes is important for anything that goes beyond a simple cube. For our pipe we want to have a single bounding box in the middle of the part and six more for every side connection of the pipe. CBMP gives us the possibility to create each box that a part can have individually. (Bounding, Collision and Occlusion)
The Bounding-boxes, which are called SubParts in CBMP, are used to generate boxes the player can interact with. They are also used to render the parts outline which can also be rendered individually.
The Collision-boxes are used for collision testing with Entity's. It determines where you can stand on the part and where you can walk through.
The Occlusion-boxes are used to test if your part collides with another so nothing else can be placed at the position of your part.
The Hollow Cover is a good example for the use of different boxes since it uses a single Collision and Bounding-box but four occlusion boxes to create the hole in the middle.
You can create multiple boxes by adding this simple code to your TMultiPart: (ExamplePipe)
class ExamplePipe extends TMultiPart with TNormalOcclusion {
//We have to return the pipes register name a last time.
override def getType = "examplePipe"
//Here we generate the collision boxes.
def generateCollisionBoxes = {
import mlb.technica.experience.PipeBoxes._
var boxes = Seq(oBounds(6))
for (s <- 0 until 6) boxes :+= oBounds(s)
boxes
}
//Here we want to return the collision boxes generated in generateCollisionBoxes(). We have to convert it to java since it is expected.
override def getCollisionBoxes = {
generateCollisionBoxes.asJava
}
//This method returns any occlusion boxes for the part to check if another part can be placed in the same space as this one. We will just return the middle box for now as nothing can connect yet.
//But we already added the structure for multiple boxes.
override def getOcclusionBoxes = {
import mlb.technica.experience.PipeBoxes._
if (false)
else Seq(oBounds(6)).asJava
}
//This method is used to return any sub parts that can be broken or clicked by the player to render their outlines. The connections the pipe makes to its sides are such subparts.
override def getSubParts = {
val b = generateCollisionBoxes
var i = Seq[indexedCuboid6]()
for (c <- b) i :+= new IndexedCuboid6(0, c)
i.asJava
}
}
//This Object stores all the possible boxes.
object PipeBoxes {
var oBounds = {
val boxes = new Array[Cuboid6](7)
val w = 2/8D
boxes(6) = new Cuboid6(0.5-w, 0.5-w, 0.5-w, 0.5+w, 0.5+w, 0.5+w)
for (s <- 0 until 6)
boxes(s) = new Cuboid6(0.5-w, 0, 0.5-w, 0.5+w, 0.5-w, 0.5+w).apply(Rotation.sideRotations(s).at(Vector3.center))
boxes
}
}
This should provide a simple way to add more boxes to the part, by creating arrays of Cuboids. In the next tutorial we want to make them connect automatically.
[spoiler=Rendering]
CBMP has two main methods to render the part in the world but the bounding-box renderer can also be overridden.
The part can be rendered using static and dynamic rendering. The static renderer uses the world renderer and only updates every time something in the world changes while the dynamic renderer updates every frame. It can be used for moving things as it also passes the partialTickTime.
First of all we want to take a look at the static renderer.
When using the static renderer the Tessellator is already running in quad mode and only draws when the world has rendered.
It can be accessed by overriding the renderStatic method in the part. We also have to return if vertecies were added to the Tessellator.
@SideOnly(Side.CLIENT)
override def renderStatic(pos: Vector3, pass: Int) = {
TextureUtils.bindAtlas(0) //First of all we want to bind the Block texture atlas just to be super safe...
/*
* All of your rendering code can go here but we have to register and use a texture first...
*/
}
Now that we have the renderer set up we can't simply bind a texture as it would also apply to every other block rendered by the world renderer. But we can register a texture to the main texture atlas where all the block textures are stored. To do so we can simply use the Item we created for the part.
class ItemPartExamplePipe extends Item with TItemMultiPart {
val sprites = new Array[iIcon](1) //For some reason it won't let me use a simple IIcon but we can use this for other parts...
def newPart(item:ItemStack, player:EntityPlayer, world:World, pos:BlockCoord, side:Int, vhit:Vector3) = {
val w = MultiPartRegistry.createPart("examplemod_examplepipe", false).asInstanceOf[examplepipe]
w
}
@SideOnly(Side.CLIENT)
override def registerIcons(reg:IIconRegister) {
sprites(0) = reg.registerIcon("modid:parts/TextureName") //The texture is stored at assets/modid/textures/blocks/parts
}
/**
* Returns 0 for /terrain.png, 1 for /gui/items.png
* We use it to make the item not register the texture in the item texture atlas.
*/
@SideOnly(Side.CLIENT)
override def getSpriteNumber = 0
}
Now that we have the texture IIcon we can get the textures UV coordinates from it and use it for the tessellator.
uMin = sprites(0).getMinU
uMax = sprites(0).getMaxU
vMin = sprites(0).getMinV
vMax = sprites(0).getMaxV
When we want to use the dynamic renderer we just have to start / draw / stop it. We can use the CCRenderState Library to do so. It also has methods for light adjusting and transparency, which might come in handy when you have light problems.
override def renderDynamic(pos: Vector3, frame: Float, pass: Int): Unit = {
TextureUtils.bindAtlas(0)
CCRenderState.startDrawing()
// Render stuff
CCRenderState.draw()
}
Finally make sure that you only render solid stuff when the pass var is 1 and only render transparent stuff when the pass is 1. Never render stuff at both passes as it would just cause framerate lag. When you don't render something make sure to return false, otherwise the game will crash.
More Tutorials will follow weekly...
I apologize for any English mistakes. Please write a comment about them and any error you get in the code so I can improve the Tutorial.
Overall Credits:
Chickenbones: For the great code.
MrTJP: For the great example that is ProjectRed.
Any tutorials without credit are by me.
Edited by Lex, CBMP has no realtion to Forge, so make that clean and changed FMP to CBMP.
Go to: Help / Install New Software
[spoiler=Image]
Type the Update URL for the Scala plugin from http://scala-ide.org/download/current.html into the "Work with" field and click 'Add...'
[spoiler=Image]
Choose "Scala" as name and click 'OK'
Select all components and click 'Next'
[spoiler=Image]
Click 'Next' again and accept all Licenses. Last but not least click 'Finish' to Install the plugins.
Finally right-click on your project and choose Configure / Add Scala Nature from the context menu.