Minecraft Modding: Quick Tips For Blocks

Get A List Of All Registered Blocks (With Variants)


Thanks to diesieben07 for this tip. Sometime after all mods have gone through the registry events, if you want all possible block states of a block you can use Block::getBlockState().getValidStates(), which will give you a List. So if you want all valid states of all blocks you can use a flat map:

ForgeRegistries.BLOCKS.getValues().stream().flatMap(block -> block.getBlockState().getValidStates()).collect(Collectors.toList()

Blocks That Are Color Tinted (Like Grass And Leaves)


Minecraft allows some blocks to be tinted (i.e. have a color multiplied on the texture) while being rendered. This is used in vanilla grass and leaves to adjust colors for biome-specific color schemes.

Forge has provided the IBlockColor interface to allow modders to do the same thing with their custom blocks. The steps to create a custom block that can be tinted are:

  1. Create a block normally. I'll assume you know how to do that.
  2. In the JSON model file for the block, make sure to use the "tintindex" element to allow the block to be tinted. You can also get this by making the parent model one which has the "tintindex" used. See example code
  3. Create a custom class that implements the IBlockColor interface, which means implementing the colorMultiplier() method. The multiplier can be set based on whatever you want, often it is biome but in my example I'm actually setting colors under the player. See example code.
  4. In your ClientProxy's init-handling method, register your blocks against your IBlockColor implementation. You get the registry using the Minecraft.getMinecraft().getBlockColors() and then invoke its registerBlockColorHandler() method. See example method.
Tip: Normally the tinting is for in-world blocks based on something like biome. But you might possibly want to tint the ItemBlock as well. In that case you need to create an IItemColor implementation that gets the colour from BlockColors and then register that with ItemColors. You can see an example of this in Choonster's test mod.

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 describe 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.

Unfortunately, the Forge events are primarily designed to allow modders to fully replace the vanilla stuff. So you can call your own generators, decorators, structures, and so forth. But there isn't any elegant way to scan all the blocks after generation to do replacement.

However, if you look at ChunkEvent.Load event you'll find that it is called not just upon loading but also immediately after chunk generation. So it is the best place to hook into. 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.)

So here is an example:

public static Block fromBlock = Blocks.GRASS; // change this to suit your need
public static Block toBlock = Blocks.SLIME_BLOCK; // change this to suit your need
     
@SubscribeEvent(priority=EventPriority.NORMAL, receiveCanceled=true)
public static void onEvent(ChunkEvent.Load event)
{ 
      
  Chunk theChunk = event.getChunk();
      
  // replace all blocks of a type with another block type

  for (int x = 0; x < 16; ++x) 
  {
     for (int z = 0; z < 16; ++z) 
     {
         for (int y = 0; y < theChunk.getHeightValue(x, z)+1; ++y) 
         {
            if (theChunk.getBlockState(x, y, z).getBlock() == fromBlock)
            {
                theChunk.setBlockState(new BlockPos(x, y, z), toBlock.getDefaultState());
            }
          }
      }
  }
  theChunk.markDirty();
}

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.

Note: This will only replace generated blocks. If you really want to replace a block completely in the game you need to also substitute it in the creative menus, possibly intercept any block placement, and if there are recipes that output the block you'll need to replace those.

Tip: Depending on what you want to replace you may want to check different Y-values. Since I was replacing a block normally found on the surface I only looked 20 blocks below the top block. I didn't just look at the top block because in cases of floating islands and overhanging cliffs there were cases where the grass block was not technically the top block.

Warning: If you don't call the markDirty() method, 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