Minecraft Modding: Quick Tips For Blocks

Blocks With Flexible Models


In 1.8+ the JSON model approach has made it tedious for those who have blocks that may have a large number of model variations or states. The answer to this is to use ISmartBlockModel and ExtendedBlockState and related model baking classes.

See a great tutorial on this at Herbix's Flexible Block Model Tutorial.

Rendering Transparent Blocks


See a detailed explanation: TheGreyGhost's Transparent Block Tutorial

Finding Block Under an Entity (Including Player)


See tutorial tip on this topic in my Quick Tips for Entities.

Finding Block Player Is Looking AT


See tutorial tip on this topic in my Quick Tips for Entities.

Make A Vanilla Block Unbreakable


With a custom block, you can of course directly control the tools, hardness, harvesting, etc.; however, if you want to change a vanilla block to be unbreakable (either generally or under certain conditions) you can @SubscribeEvent to the BlockEvent.BreakSpeed event and cancel that.  Of course you can cancel it for all blocks, selected blocks, or under certain conditions. For more information on event handling, see my tutorial: Jabelar's Event Handling Tutorial.

Making A Custom Block Unpushable


The Block#getMobilityFlag() method controls the reaction of the block to being pushed by pistons. By default, it returns Material#getMobilityFlag() for the block's material.

Either use a Material with the appropriate EnumPushReaction or @Override the method to return the appropriate EnumPushReaction.

Making Custom Block Drop Multiple Items


There is a getDrops() method in the Block class which you can @Override.  It returns an array list of item stacks.

Changing The Drop Of A Vanilla Block


You can change the drop by handling the HarvestDropsEvent.

Changing The Render Type Of A Custom Block


While a "block" usually makes you think of a cube, in Minecraft there are actually many, many blocks that render differently.  For example flowers, torches, pistons, doors, etc.

When you're making a custom block, you may want to choose one of these alternative render types.  For example, I wanted a crop to use the "crossed squares" type that is used by flowers.  Here's how you do that.

In any block you can @Override the getRenderType() method to set the render type, For a list of possible render types you should look at the source for the renderBlockByRenderType()method of the RenderBlocks class.  There you will see a switch statement by render type and will see that standard blocks are 0, "crossed squares" is 1, torch is 2, and so on.  You can also check the code for any block that renders the way you like and copy their render type.

Tip: You can even define your own custom render types for blocks.  See: TheGreyGhost's Custom Block Rendering Tutorial.

Changing The Bounding Box Of Any Block


This tip is thanks to rich1051414.  I have not tested it, but it is an interesting idea.

Key Point: The addCollisionBoxesToList()method is generated dynamically, is public, and can be overridden to provide and collision box you want!  Since this is called by path-finding, you can have different collision boxes depending on the entity type or other entity-related variables.

To be extra fancy, this example is to make an invisible bounding block that extends above the actual block but only for specific entities. You don't need to do something so complicated, but this just shows you the possibilities.  The key function to understand is the getSelectedBoundingBoxFromPool() which you can @Override in your custom block.

For this example, you would only need something like this in your actual block code to place a custom block that will act as an invisible gate (i.e the bounding box will vary depending on entity):

@Override
public void onBlockAdded(World par1World, int parX, int parY, int parZ) 
{
    super.onBlockAdded(parWorld, parX, parY, parZ);
    if(parWorld.isAirBlock(parX, parY + 1, parZ))
    {
        parWorld.setBlock(parX, parY + 1, parZ, MyBlocks.pathBlockingBlock);
    }
    if(parWorld.isAirBlock(parX, parY + 2, parZ))
    {
        parWorld.setBlock(parX, parY + 2, parZ, MyBlocks.pathBlockingBlock);
    }
}

@Override
public void onNeighborBlockChange(World parWorld, int parX, int parY, int parZ, int par5)
{
    if(parWorld.isAirBlock(parX, parY + 1, parZ))
    {
        parWorld.setBlock(parX, parY + 1, parZ, MyBlocks.pathBlockingBlock);
    }
    if(parWorld.isAirBlock(parX, parY + 2, parZ))
    {
        parWorld.setBlock(parX, parY + 2, parZ, MyBlocks.pathBlockingBlock);
    }
}

Then for your invisibly rendered path blocking block (note due to the getAABBPool().getAABB() method this is a 1.7.2 example and may need to be updated to getBoundingBox() for 1.7.10):

//This is not a normal block.
public boolean renderAsNormalBlock()
{
    return false;
}

//Let all light pass through
public int getLightOpacity(World world, int x, int y, int z) 
{
    return 0;
}

//Set block as replaceable.
public boolean isBlockReplaceable(World world, int x, int y, int z) 
{
    return true;
}

//0 width length and height box so no wireframe rendered.
public AxisAlignedBB getSelectedBoundingBoxFromPool(World par1World, int par2, int par3, int par4)
{
    return AxisAlignedBB.getAABBPool().getAABB((double)par2, (double)par3, (double)par4, (double)par2, (double)par3, (double)par4);
}

//Check if the invisible block needs to cleanup
public void onNeighborBlockChange(World par1World, int par2, int par3, int par4, int par5)
{
   if(par1World.getBlock(par2, par3 - 1, par4) != myBlock && par1World.getBlock(par2, par3 - 2, par4) != myBlock)
   {
      par1World.setBlockToAir(par2, par3, par4);
   }
}

//Only block specific mobs
public void addCollisionBoxesToList(World par1World, int par2, int par3, int par4, 
      AxisAlignedBB par5AxisAlignedBB, List par6List, Entity par7Entity) 
{
   if(par7Entity instanceof EntityMob)
   {//Or whichever mob you want to block
      super.addCollisionBoxesToList(par1World, par2, par3, par4, par5AxisAlignedBB, par6List, par7Entity);
   }
}

//So Raytracing passes through.
public MovingObjectPosition collisionRayTrace(World par1World, int par2, int par3, 
      int par4, Vec3 par5Vec3, Vec3 par6Vec3) 
{
   return null;
}

//Nothing needs to be done here like there is no block here.
public void onBlockClicked(World par1World, int par2, int par3, int par4, EntityPlayer par5EntityPlayer) {}

Warning: If you use this technique to change the collision box such that it allows the player or entities to go through, you need to ensure renderAsNormalBlock() returns false, otherwise the entity will take suffocation damage in the block (and also get sort of pushed out from the block).

Create A Mountable Block


Thanks to nexusrightsi for this tip!

You may find you want to make a block that acts like a chair. The general method to do this is to, when player right-clicks a block, create an invisible, non-collidable Entity (make sure you extend Entity, not EntityCreature, EntityLiving, etc.) with appropriate bounding box and immediately dismount it. Call setDead() on the invisible Entity when the player dismounts. 

Make An Underwater Block Like Seaweed


Thanks to Choonster for this tip.

You might want to make a block that is under water like seaweed. To get that to render properly you need to consider a couple points.


  • Your block's model will be rendered with water surrounding it if its Material is Material.WATER.
  • A lot of vanilla code expects blocks that use Material.WATER to have the BlockLiquid.LEVEL property, so your block needs to include this. It doesn't need to be saved to the metadata, just set the default value to 0 and override Block#getMetaFromState to ignore it.
  • Even if you set the water level to 0, you will need to have each of the block state models for levels 0 to 6 since the LEVEL property contains all those levels.


You can see an example of this here.

Replace All Generated Blocks With Different Block


There is supposed to be a mechanism called a substitution alias that does this for you, but I've never got that to work. After 1.10.2 substitution is now working but I haven't tried it yet. But in previous version you had to use a method like what I descrive below.

If you want to change generation in your own custom dimension, then you can simply tell your ChunkProvider to generate your own Block instead of stone. For vanilla's ChunkProviderGenerate this happens in method func_147424_a().

But what if you want to change generated blocks in other/vanilla dimensions?  Well, here's a way.

It is possible to handle the PopulateChuckEvent to check for blocks in the chunk and replace them. Here is an example.  Note that this method should be put into a handler class that is registered to the regular EVENT_BUS.  (See my tutorial on events for more information on event handling.)

// for some reason the PopulateChunkEvents are fired on the main EVENT_BUS
// even though they are in the terraingen package
@SubscribeEvent(priority=EventPriority.NORMAL, receiveCanceled=true)
public void onEvent(PopulateChunkEvent.Pre event)
{
    // replace all blocks of a type with another block type
    // diesieben07 came up with this method (http://www.minecraftforge.net/forum/index.php/topic,21625.0.html)
        
    Chunk chunk = event.world.getChunkFromChunkCoords(event.chunkX, event.chunkZ);
    Block fromBlock = Blocks.grass; // change this to suit your need
    Block toBlock = Blocks.stone; // change this to suit your need

    for (ExtendedBlockStorage storage : chunk.getBlockStorageArray()) 
    {
        if (storage != null) 
        {
            for (int x = 0; x < 16; ++x) 
            {
                for (int y = 0; y < 256; ++y) 
                {
                    for (int z = 0; z < 16; ++z) 
                    {
                        if (storage.getBlockByExtId(x, y, z) == fromBlock) 
                        {
                            storage.func_150818_a(x, y, z, toBlock);
                        }
                    }
                }
            }
        }
    }  
    chunk.isModified = true; // this is important as it marks it to be saved
}

Note: It is possible to further check for the metadata of the block being replaced, and also to set the metadata of the block being replaced.  Look in the ExtendedBlockStorage class for the related methods if needed.

Warning: If you don't set the isModified field, then each time you reload the chunk it will have to replace all the blocks again.  It will still work, but is a waste of performance.

Making A Custom Climbable ("Ladder") Block


While you can of course extend BlockLadder directly, you may have another type of block extending something else that you want to make act like a ladder -- allowing climbing.  For example, in my Magic Beans mod I made a magic beanstalk that was climbable but I couldn't just extend ladder because it was actually a crop.

There is a method in the Block class called isLadder() which you can @Override to return true.

Warning: You also have to make sure that there is a "collision" with the block, meaning you have to be able to let the player enter the block's space in order for the climbing to work.  To do that, you have to reduce the block bounds with the setBlockBounds() method in the block constructor:

setBlockBounds(0.5F-0.125F, 0.0F, 0.5F-0.125F, 0.5F+0.125F, 1.0F, 0.5F+0.125F);

Furthermore, you need to @Override the isLadder() method to always return true.

Create Different Light Levels Per Block Location


Thanks to CoolAlias for this tip.

You may have a need to have a custom block that gives off different light levels at different placements. You can change the light level per location by overriding the getLightValue() method and then using either metadata, TileEntity fields, or some other calculation (like proximity to something) to determine the returned value. 

For example if you want to control the light value based on metadata, you could do the following:


public int getLightValue(IBlockAccess world, int x, int y, int z) {
  int meta = world.getBlockMetadata(x, y, z);
  return value based on metadata;
}

No comments:

Post a Comment