Jump to content

GotPlants (Block & Plantable)


Gotlyfe

Recommended Posts

Continuing from 

I am going to be following the same format of editing the post and describing my actions to create this mod to add new plants.

To be specific, I am going to be adding new blocks, items, sounds and recipes.

 

 

Updates: 

-Adding first plant block (Bamboo)

Spoiler

First read through the documentation at https://mcforge.readthedocs.io/en/latest/blocks/blocks/

Then found some examples from

that I will be working from.

 

-Create new Package and Class named accordingly (plants.bamboo) (BlockBamboo.java)

Spoiler

I will be basing this heavily on @TheGreyGhost's TheGreyGhost MinecraftByExample03_block_variants

as well as the BlockCactus.java from net.minecraft.block

often using the documentation, so also familiarize yourself with the models section MCForge ReadTheDocs Models

 

-Make BlockBamboo extend Block and  implement IPlantable

import net.minecraft.block.Block;

public class BlockBamboo extends Block implements net.minecraftforge.common.IPlantable{
}

This requires us to add a constructor giving the Material for the block

	protected BlockBamboo()
	{
		super(Material.PLANTS);
	}

which requires import net.minecraft.block.material.Material
 

Spoiler

For now I pulled a material that seemed right from Material.class

Spoiler


    public static final Material AIR = new MaterialTransparent(MapColor.AIR);
    public static final Material GRASS = new Material(MapColor.GRASS);
    public static final Material GROUND = new Material(MapColor.DIRT);
    public static final Material WOOD = (new Material(MapColor.WOOD)).setBurning();
    public static final Material ROCK = (new Material(MapColor.STONE)).setRequiresTool();
    public static final Material IRON = (new Material(MapColor.IRON)).setRequiresTool();
    public static final Material ANVIL = (new Material(MapColor.IRON)).setRequiresTool().setImmovableMobility();
    public static final Material WATER = (new MaterialLiquid(MapColor.WATER)).setNoPushMobility();
    public static final Material LAVA = (new MaterialLiquid(MapColor.TNT)).setNoPushMobility();
    public static final Material LEAVES = (new Material(MapColor.FOLIAGE)).setBurning().setTranslucent().setNoPushMobility();
    public static final Material PLANTS = (new MaterialLogic(MapColor.FOLIAGE)).setNoPushMobility();
    public static final Material VINE = (new MaterialLogic(MapColor.FOLIAGE)).setBurning().setNoPushMobility().setReplaceable();
    public static final Material SPONGE = new Material(MapColor.YELLOW);
    public static final Material CLOTH = (new Material(MapColor.CLOTH)).setBurning();
    public static final Material FIRE = (new MaterialTransparent(MapColor.AIR)).setNoPushMobility();
    public static final Material SAND = new Material(MapColor.SAND);
    public static final Material CIRCUITS = (new MaterialLogic(MapColor.AIR)).setNoPushMobility();
    public static final Material CARPET = (new MaterialLogic(MapColor.CLOTH)).setBurning();
    public static final Material GLASS = (new Material(MapColor.AIR)).setTranslucent().setAdventureModeExempt();
    public static final Material REDSTONE_LIGHT = (new Material(MapColor.AIR)).setAdventureModeExempt();
    public static final Material TNT = (new Material(MapColor.TNT)).setBurning().setTranslucent();
    public static final Material CORAL = (new Material(MapColor.FOLIAGE)).setNoPushMobility();
    public static final Material ICE = (new Material(MapColor.ICE)).setTranslucent().setAdventureModeExempt();
    public static final Material PACKED_ICE = (new Material(MapColor.ICE)).setAdventureModeExempt();
    public static final Material SNOW = (new MaterialLogic(MapColor.SNOW)).setReplaceable().setTranslucent().setRequiresTool().setNoPushMobility();
    /** The material for crafted snow. */
    public static final Material CRAFTED_SNOW = (new Material(MapColor.SNOW)).setRequiresTool();
    public static final Material CACTUS = (new Material(MapColor.FOLIAGE)).setTranslucent().setNoPushMobility();
    public static final Material CLAY = new Material(MapColor.CLAY);
    public static final Material GOURD = (new Material(MapColor.FOLIAGE)).setNoPushMobility();
    public static final Material DRAGON_EGG = (new Material(MapColor.FOLIAGE)).setNoPushMobility();
    public static final Material PORTAL = (new MaterialPortal(MapColor.AIR)).setImmovableMobility();
    public static final Material CAKE = (new Material(MapColor.AIR)).setNoPushMobility();
    public static final Material WEB = (new Material(MapColor.CLOTH)
	/** Pistons' material. */
    public static final Material PISTON = (new Material(MapColor.STONE)).setImmovableMobility();
    public static final Material BARRIER = (new Material(MapColor.AIR)).setRequiresTool().setImmovableMobility();
    public static final Material STRUCTURE_VOID = new MaterialTransparent(MapColor.AIR);

 

This isn't related to texture

 

-Need Imports for next steps

import net.minecraft.block.properties.PropertyInteger;
import net.minecraft.block.state.IBlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World;

-Define an age property before constructor inside class to determine growth

public static final PropertyInteger AGE = PropertyInteger.create("age", 0, 7);

-Initialize the age to 0 inside of our constructor

this.setDefaultState(this.blockState.getBaseState().withProperty(AGE, Integer.valueOf(0)));

 

-IPlantable requires us to implement EnumPlantType getPlantType() and IBlockState getPlant() Both of which are just asking for more metadata about the plant

	@Override
	public EnumPlantType getPlantType(IBlockAccess world, BlockPos pos)
	{	return net.minecraftforge.common.EnumPlantType.Beach;	}

	@Override
	public IBlockState getPlant(IBlockAccess world, BlockPos pos) 
	{	return getDefaultState();	}
Spoiler

The list of return choices from EnumPlantType.class is Plains, Desert, Beach, Cave, Water, Nether or Crop
getPlant just wants the block state from our block. Documentation here https://mcforge.readthedocs.io/en/latest/models/blockstates/forgeBlockstates/#

 

 

-A quick thing to do is set creative tab

import net.minecraft.creativetab.CreativeTabs;

and in the constructor for our block 

this.setCreativeTab(CreativeTabs.DECORATIONS);

 

-Need Random Ticks for Growth, Add inside constructor

this.setTickRandomly(true);

-Then we need an UpdateTick function to tell it to grow

Spoiler

Full code for updateTick


 public void updateTick(World worldIn, BlockPos pos, IBlockState state, Random rand)
 {
   if (!worldIn.isAreaLoaded(pos, 1)) return; // Forge: prevent growing cactus from loading unloaded chunks with block update
   BlockPos blockpos = pos.up();

   if (worldIn.isAirBlock(blockpos))
   {
     int i;

     for (i = 1; worldIn.getBlockState(pos.down(i)).getBlock() == this; ++i)
     {
       ;
     }

     if (i < 6)//Maximum Height
     {
       int j = ((Integer)state.getValue(AGE)).intValue();

       if(net.minecraftforge.common.ForgeHooks.onCropsGrowPre(worldIn, blockpos, state, true))
       {
         if (j == 7)
         {
           worldIn.setBlockState(blockpos, this.getDefaultState());
           IBlockState iblockstate = state.withProperty(AGE, Integer.valueOf(0));
           worldIn.setBlockState(pos, iblockstate, 4);
           iblockstate.neighborChanged(worldIn, blockpos, this, pos);
         }
         else
         {
           worldIn.setBlockState(pos, state.withProperty(AGE, Integer.valueOf(j + 1)), 4);
         }
         net.minecraftforge.common.ForgeHooks.onCropsGrowPost(worldIn, pos, state, worldIn.getBlockState(pos));
       }
     }
   }
 }

public void neighborChanged(IBlockState state, World worldIn, BlockPos pos, Block blockIn, BlockPos fromPos)
{	}

 

 

 

-Which was pulled from the BlockCactus.class with only the maximum height, max age and neighborChanged edited

Spoiler

worldIn.isAreaLoaded 

is to determine if the plant is currently in a loaded chunk
 


BlockPos blockpos = pos.up();
        if (worldIn.isAirBlock(blockpos))

determines if the block above our plant is air

 


for (i = 1; worldIn.getBlockState(pos.down(i)).getBlock() == this; ++i)
  if (i < 6)

Checks how many tall our plant is, maximum it lets it grow is 6

 


int j = ((Integer)state.getValue(AGE)).intValue();
if(net.minecraftforge.common.ForgeHooks.onCropsGrowPre(worldIn, blockpos, state, true))
{	if (j == 7)

pregrowth event true if allowing growth

checks age
 


worldIn.setBlockState(blockpos, this.getDefaultState());
IBlockState iblockstate = state.withProperty(AGE, Integer.valueOf(0));
worldIn.setBlockState(pos, iblockstate, 4);

Sets the new block and resets growth states

 


iblockstate.neighborChanged(worldIn, blockpos, this, pos);

We use this later on when we want to change the model for being next to other blocks

 


else
{	worldIn.setBlockState(pos, state.withProperty(AGE, Integer.valueOf(j + 1)), 4);		}

Otherwise ages the plant and finishes with after crop growth event


net.minecraftforge.common.ForgeHooks.onCropsGrowPost(worldIn, pos, state, worldIn.getBlockState(pos));

 

 

We're leaving neightborChanged definition empty for now


-Variable Model

-Pulling directly from BlockWall.class

Spoiler

Imports


import java.util.List;
import javax.annotation.Nullable;
import net.minecraft.util.IStringSerializable;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.NonNullList;
import net.minecraft.util.EnumFacing;
import net.minecraft.item.ItemStack;
import net.minecraft.init.Blocks;
import net.minecraft.entity.Entity;
import net.minecraft.block.BlockFenceGate;
import net.minecraft.block.state.BlockFaceShape;
import net.minecraft.block.state.BlockStateContainer;
import net.minecraft.block.state.IBlockState;
import net.minecraft.block.properties.IProperty;
import net.minecraft.block.properties.PropertyBool;
import net.minecraft.block.properties.PropertyEnum;

Static Variables in Class


public static final PropertyBool UP = PropertyBool.create("up");
public static final PropertyBool NORTH = PropertyBool.create("north");
public static final PropertyBool EAST = PropertyBool.create("east");
public static final PropertyBool SOUTH = PropertyBool.create("south");
public static final PropertyBool WEST = PropertyBool.create("west");
public static final PropertyEnum<BlockBamboo.EnumType> VARIANT = PropertyEnum.<BlockBamboo.EnumType>create("variant", BlockBamboo.EnumType.class);
protected static final AxisAlignedBB[] AABB_BY_INDEX = new AxisAlignedBB[] {new AxisAlignedBB(0.25D, 0.0D, 0.25D, 0.75D, 1.0D, 0.75D), new AxisAlignedBB(0.25D, 0.0D, 0.25D, 0.75D, 1.0D, 1.0D), new AxisAlignedBB(0.0D, 0.0D, 0.25D, 0.75D, 1.0D, 0.75D), new AxisAlignedBB(0.0D, 0.0D, 0.25D, 0.75D, 1.0D, 1.0D), new AxisAlignedBB(0.25D, 0.0D, 0.0D, 0.75D, 1.0D, 0.75D), new AxisAlignedBB(0.3125D, 0.0D, 0.0D, 0.6875D, 0.875D, 1.0D), new AxisAlignedBB(0.0D, 0.0D, 0.0D, 0.75D, 1.0D, 0.75D), new AxisAlignedBB(0.0D, 0.0D, 0.0D, 0.75D, 1.0D, 1.0D), new AxisAlignedBB(0.25D, 0.0D, 0.25D, 1.0D, 1.0D, 0.75D), new AxisAlignedBB(0.25D, 0.0D, 0.25D, 1.0D, 1.0D, 1.0D), new AxisAlignedBB(0.0D, 0.0D, 0.3125D, 1.0D, 0.875D, 0.6875D), new AxisAlignedBB(0.0D, 0.0D, 0.25D, 1.0D, 1.0D, 1.0D), new AxisAlignedBB(0.25D, 0.0D, 0.0D, 1.0D, 1.0D, 0.75D), new AxisAlignedBB(0.25D, 0.0D, 0.0D, 1.0D, 1.0D, 1.0D), new AxisAlignedBB(0.0D, 0.0D, 0.0D, 1.0D, 1.0D, 0.75D), new AxisAlignedBB(0.0D, 0.0D, 0.0D, 1.0D, 1.0D, 1.0D)};
protected static final AxisAlignedBB[] CLIP_AABB_BY_INDEX = new AxisAlignedBB[] {AABB_BY_INDEX[0].setMaxY(1.5D), AABB_BY_INDEX[1].setMaxY(1.5D), AABB_BY_INDEX[2].setMaxY(1.5D), AABB_BY_INDEX[3].setMaxY(1.5D), AABB_BY_INDEX[4].setMaxY(1.5D), AABB_BY_INDEX[5].setMaxY(1.5D), AABB_BY_INDEX[6].setMaxY(1.5D), AABB_BY_INDEX[7].setMaxY(1.5D), AABB_BY_INDEX[8].setMaxY(1.5D), AABB_BY_INDEX[9].setMaxY(1.5D), AABB_BY_INDEX[10].setMaxY(1.5D), AABB_BY_INDEX[11].setMaxY(1.5D), AABB_BY_INDEX[12].setMaxY(1.5D), AABB_BY_INDEX[13].setMaxY(1.5D), AABB_BY_INDEX[14].setMaxY(1.5D), AABB_BY_INDEX[15].setMaxY(1.5D)};

Functions


    public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess source, BlockPos pos)
    {
        state = this.getActualState(state, source, pos);
        return AABB_BY_INDEX[getAABBIndex(state)];
    }

    public void addCollisionBoxToList(IBlockState state, World worldIn, BlockPos pos, AxisAlignedBB entityBox, List<AxisAlignedBB> collidingBoxes, @Nullable Entity entityIn, boolean isActualState)
    {
        if (!isActualState)
        {
            state = this.getActualState(state, worldIn, pos);
        }

        addCollisionBoxToList(pos, entityBox, collidingBoxes, CLIP_AABB_BY_INDEX[getAABBIndex(state)]);
    }

    @Nullable
    public AxisAlignedBB getCollisionBoundingBox(IBlockState blockState, IBlockAccess worldIn, BlockPos pos)
    {
        blockState = this.getActualState(blockState, worldIn, pos);
        return CLIP_AABB_BY_INDEX[getAABBIndex(blockState)];
    }

    private static int getAABBIndex(IBlockState state)
    {
        int i = 0;

        if (((Boolean)state.getValue(NORTH)).booleanValue())
        {
            i |= 1 << EnumFacing.NORTH.getHorizontalIndex();
        }

        if (((Boolean)state.getValue(EAST)).booleanValue())
        {
            i |= 1 << EnumFacing.EAST.getHorizontalIndex();
        }

        if (((Boolean)state.getValue(SOUTH)).booleanValue())
        {
            i |= 1 << EnumFacing.SOUTH.getHorizontalIndex();
        }

        if (((Boolean)state.getValue(WEST)).booleanValue())
        {
            i |= 1 << EnumFacing.WEST.getHorizontalIndex();
        }

        return i;
    }
      
    public boolean isFullCube(IBlockState state)
    {
        return false;
    }

    /**
     * Determines if an entity can path through this block
     */
    public boolean isPassable(IBlockAccess worldIn, BlockPos pos)
    {
        return false;
    }

    /**
     * Used to determine ambient occlusion and culling when rebuilding chunks for render
     */
    public boolean isOpaqueCube(IBlockState state)
    {
        return false;
    }

    private boolean canConnectTo(IBlockAccess worldIn, BlockPos pos, EnumFacing p_176253_3_)
    {
        IBlockState iblockstate = worldIn.getBlockState(pos);
        Block block = iblockstate.getBlock();
        BlockFaceShape blockfaceshape = iblockstate.getBlockFaceShape(worldIn, pos, p_176253_3_);
        boolean flag = blockfaceshape == BlockFaceShape.MIDDLE_POLE_THICK || blockfaceshape == BlockFaceShape.MIDDLE_POLE && block instanceof BlockFenceGate;
        return !isExcepBlockForAttachWithPiston(block) && blockfaceshape == BlockFaceShape.SOLID || flag;
    }

    protected static boolean isExcepBlockForAttachWithPiston(Block p_194143_0_)
    {
        return Block.isExceptBlockForAttachWithPiston(p_194143_0_) || p_194143_0_ == Blocks.BARRIER || p_194143_0_ == Blocks.MELON_BLOCK || p_194143_0_ == Blocks.PUMPKIN || p_194143_0_ == Blocks.LIT_PUMPKIN;
    }

    /**
     * returns a list of blocks with the same ID, but different meta (eg: wood returns 4 blocks)
     */
    public void getSubBlocks(CreativeTabs itemIn, NonNullList<ItemStack> items)
    {
        for (BlockBamboo.EnumType blockbamboo$enumtype : BlockBamboo.EnumType.values())
        {
            items.add(new ItemStack(this, 1, blockbamboo$enumtype.getMetadata()));
        }
    }

    /**
     * Gets the metadata of the item this Block can drop. This method is called when the block gets destroyed. It
     * returns the metadata of the dropped item based on the old metadata of the block.
     */
    public int damageDropped(IBlockState state)
    {
        return ((BlockBamboo.EnumType)state.getValue(VARIANT)).getMetadata();
    }
    /**
     * Convert the given metadata into a BlockState for this Block
     */
    public IBlockState getStateFromMeta(int meta)
    {
        return this.getDefaultState().withProperty(VARIANT, BlockBamboo.EnumType.byMetadata(meta));
    }

    /**
     * Convert the BlockState into the correct metadata value
     */
    public int getMetaFromState(IBlockState state)
    {
        return ((BlockBamboo.EnumType)state.getValue(VARIANT)).getMetadata();
    }

    /**
     * Get the actual Block state of this Block at the given position. This applies properties not visible in the
     * metadata, such as fence connections.
     */
    public IBlockState getActualState(IBlockState state, IBlockAccess worldIn, BlockPos pos)
    {
        boolean flag =  canWallConnectTo(worldIn, pos, EnumFacing.NORTH);
        boolean flag1 = canWallConnectTo(worldIn, pos, EnumFacing.EAST);
        boolean flag2 = canWallConnectTo(worldIn, pos, EnumFacing.SOUTH);
        boolean flag3 = canWallConnectTo(worldIn, pos, EnumFacing.WEST);
        boolean flag4 = flag && !flag1 && flag2 && !flag3 || !flag && flag1 && !flag2 && flag3;
        return state.withProperty(UP, Boolean.valueOf(!flag4 || !worldIn.isAirBlock(pos.up()))).withProperty(NORTH, Boolean.valueOf(flag)).withProperty(EAST, Boolean.valueOf(flag1)).withProperty(SOUTH, Boolean.valueOf(flag2)).withProperty(WEST, Boolean.valueOf(flag3));
    }

    protected BlockStateContainer createBlockState()
    {
        return new BlockStateContainer(this, new IProperty[] {UP, NORTH, EAST, WEST, SOUTH, VARIANT});
    }

    /**
     * Get the geometry of the queried face at the given position and state. This is used to decide whether things like
     * buttons are allowed to be placed on the face, or how glass panes connect to the face, among other things.
     * <p>
     * Common values are {@code SOLID}, which is the default, and {@code UNDEFINED}, which represents something that
     * does not fit the other descriptions and will generally cause other things not to connect to the face.
     * 
     * @return an approximation of the form of the given face
     */
    public BlockFaceShape getBlockFaceShape(IBlockAccess worldIn, IBlockState state, BlockPos pos, EnumFacing face)
    {
        return face != EnumFacing.UP && face != EnumFacing.DOWN ? BlockFaceShape.MIDDLE_POLE_THICK : BlockFaceShape.CENTER_BIG;
    }

    /* ======================================== FORGE START ======================================== */

    @Override
    public boolean canBeConnectedTo(IBlockAccess world, BlockPos pos, EnumFacing facing)
    {
        return canConnectTo(world, pos.offset(facing), facing.getOpposite());
    }

    private boolean canWallConnectTo(IBlockAccess world, BlockPos pos, EnumFacing facing)
    {
        BlockPos other = pos.offset(facing);
        Block block = world.getBlockState(other).getBlock();
        return block.canBeConnectedTo(world, other, facing.getOpposite()) || canConnectTo(world, other, facing.getOpposite());
    }

    /* ======================================== FORGE END ======================================== */

BlockBamboo.EnumType


public static enum EnumType implements IStringSerializable
    {
        NORMAL(0, "cobblestone", "normal"),
        DRY(1, "mossy_cobblestone", "dry");

        private static final BlockBamboo.EnumType[] META_LOOKUP = new BlockBamboo.EnumType[values().length];
        private final int meta;
        private final String name;
        private final String unlocalizedName;

        private EnumType(int meta, String name, String unlocalizedName)
        {
            this.meta = meta;
            this.name = name;
            this.unlocalizedName = unlocalizedName;
        }

        public int getMetadata()
        {
            return this.meta;
        }

        public String toString()
        {
            return this.name;
        }

        public static BlockBamboo.EnumType byMetadata(int meta)
        {
            if (meta < 0 || meta >= META_LOOKUP.length)
            {
                meta = 0;
            }

            return META_LOOKUP[meta];
        }

        public String getName()
        {
            return this.name;
        }

        public String getUnlocalizedName()
        {
            return this.unlocalizedName;
        }

        static
        {
            for (BlockBamboo.EnumType blockbamboo$enumtype : values())
            {
                META_LOOKUP[blockbamboo$enumtype.getMetadata()] = blockbamboo$enumtype;
            }
        }
    }

 

This part from https://mcforge.readthedocs.io/en/latest/models/blockstates/forgeBlockstates/

used for block_bamboo_model.json


{
    "forge_marker": 1,
    "defaults": {
      "textures": {
        "texture": "plants:block/block_bamboo"
      },
      "model": "wall",
      "uvlock": true
    },
    "variants": {
      "__comment": "dry is a boolean property.",
      "dry": {
        "true": {
          "__comment": "If true it changes the bamboo from normal to dry.",
          "textures": {
            "texture": "plants:block/block_bamboo_dry"
          }
        },
        "false": {
          "__comment": "Change nothing. The entry has to be here so the Forge blockstate loader knows to generate this variant."
        }
      }
    },
    "north":{
    "when": { "north": "true" },
        "apply": { "model": "wall_n", "uvlock": true }
    },
    "east":{
    "when": { "east": "true" },
        "apply": { "model": "wall_n", "y": 90, "uvlock": true }
    },
    "south":{
    "when": { "south": "true" },
        "apply": { "model": "wall_n", "y": 180, "uvlock": true }
    },
    "west":{
    "when": { "west": "true" },
        "apply": { "model": "wall_n", "y": 270, "uvlock": true }
    }
}

 

Will go through piece by piece once I understand it all.

 


--Taking breather to work on another mod

Edited by Gotlyfe
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.