Jump to content

Recommended Posts

Posted

Alright, so I created a custom tree in 1.14.4 and it works fine, but when I updated to 1.15.2 I had a few errors and had to switch from using a no_feature_config to a tree_feature_config (though I still don't use that config in my custom tree generation). When I generate a tree using my custom sapling, everything is fine except that the sapling does not get replaced by wood. When I generated my tree as a normal minecraft tree with my tree's config I had no such issues, it is only when I generate the tree using my tree's features. Another thing I've noticed is that my custom trees generate perfectly fine in natural world, so this is only a problem when generating my tree from a sapling. 

 

Mangrove Sapling init

	public static final Block MANGROVE_SAPLING = new MangroveSaplingBlock(new MangroveTree(), Block.Properties.create(Material.PLANTS).doesNotBlockMovement().tickRandomly().hardnessAndResistance(0.0F).sound(SoundType.PLANT).notSolid());

Mangrove Sapling class

package io.github.killerjdog51.biome_enhancments.blocks;

import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.IWaterLoggable;
import net.minecraft.block.SaplingBlock;
import net.minecraft.block.trees.Tree;
import net.minecraft.fluid.Fluids;
import net.minecraft.fluid.IFluidState;
import net.minecraft.item.BlockItemUseContext;
import net.minecraft.state.BooleanProperty;
import net.minecraft.state.StateContainer;
import net.minecraft.state.properties.BlockStateProperties;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IWorld;
import net.minecraft.world.World;

public class MangroveSaplingBlock extends SaplingBlock  implements IWaterLoggable {

	public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED;

	public MangroveSaplingBlock(Tree p_i48337_1_, Properties properties) {
		super(p_i48337_1_, properties);
		this.setDefaultState(this.stateContainer.getBaseState().with(WATERLOGGED, Boolean.valueOf(false)));
	}

	@Override
	public IFluidState getFluidState(BlockState state)
	{
	      return state.get(WATERLOGGED) ? Fluids.WATER.getStillFluidState(false) : super.getFluidState(state);
	}
	
	@Override
	public void onBlockAdded(BlockState state, World worldIn, BlockPos pos, BlockState oldState, boolean isMoving)
	{
		worldIn.getPendingFluidTicks().scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickRate(worldIn));
	}
	
	@Override
	public BlockState getStateForPlacement(BlockItemUseContext context) {
	      BlockPos blockpos = context.getPos();
	      IFluidState ifluidstate = context.getWorld().getFluidState(blockpos);
	      BlockState blockstate = this.getDefaultState().with(WATERLOGGED, Boolean.valueOf(ifluidstate.getFluid() == Fluids.WATER));
	      return blockstate;
	   }
	
	@Override
	public BlockState updatePostPlacement(BlockState stateIn, Direction facing, BlockState facingState, IWorld worldIn, BlockPos currentPos, BlockPos facingPos)
	{
		if (stateIn.get(WATERLOGGED)) {
			worldIn.getPendingFluidTicks().scheduleTick(currentPos, Fluids.WATER, Fluids.WATER.getTickRate(worldIn));
		}
		return super.updatePostPlacement(stateIn, facing, facingState, worldIn, currentPos, facingPos);
	}
	
	@Override
	public boolean receiveFluid(IWorld worldIn, BlockPos pos, BlockState state, IFluidState fluidStateIn)
	{
		return IWaterLoggable.super.receiveFluid(worldIn, pos, state, fluidStateIn);
	}
	
	@Override
	protected void fillStateContainer(StateContainer.Builder<Block, BlockState> builder)
	{
	      builder.add(WATERLOGGED);
	      super.fillStateContainer(builder);
	}
}

Mangrove Tree class

package io.github.killerjdog51.biome_enhancments.blocks.trees;

import java.util.Random;
import javax.annotation.Nullable;
import io.github.killerjdog51.biome_enhancments.world.gen.feature.WorldFeatures;
import net.minecraft.block.trees.Tree;
import net.minecraft.world.gen.feature.ConfiguredFeature;
import net.minecraft.world.gen.feature.TreeFeatureConfig;

public class MangroveTree extends Tree {
   @Nullable
   protected ConfiguredFeature<TreeFeatureConfig, ?> getTreeFeature(Random randomIn, boolean p_225546_2_) {
      return WorldFeatures.MANGROVE_TREE.withConfiguration(WorldFeatures.MANGROVE_TREE_CONFIG);
   }
}

Mangrove Tree Feature class

package io.github.killerjdog51.biome_enhancments.world.gen.feature;

import com.mojang.datafixers.Dynamic;

import io.github.killerjdog51.biome_enhancments.init.ModBlocks;

import java.util.Random;
import java.util.Set;
import java.util.function.Function;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.VineBlock;
import net.minecraft.state.BooleanProperty;
import net.minecraft.util.Direction;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MutableBoundingBox;
import net.minecraft.world.gen.Heightmap;
import net.minecraft.world.gen.IWorldGenerationBaseReader;
import net.minecraft.world.gen.IWorldGenerationReader;
import net.minecraft.world.gen.feature.AbstractTreeFeature;
import net.minecraft.world.gen.feature.TreeFeatureConfig;


/**
*
* Description of Mangrove tree generation/mechanics
*
* Mangroves are a new Swamp specific tree. Like most other Minecraft trees they generate with a single trunk and have a boxy leaf top.
* Unlike other trees, Mangroves generate with vines (similar to mega jungle trees).
* The most unique feature though is that when generated under water,
* mangroves will naturally sprawl their knarly roots out from the base of the tree to create an cool-looking root mound.
* This is a possible way to get more wood from the sapling.
* Another thing to note is that the Mangrove saplings are water-logable, but won’t generate under 3 blocks of water
*/


public class MangroveTreeFeature extends AbstractTreeFeature<TreeFeatureConfig>
{
	// Some global variables
	//(generally expected to use the "final" to make the variable unchangable. But since I change the log block too wood I prefer to not finalize my log variable)
   private static BlockState LOG = ModBlocks.MANGROVE_LOG.getDefaultState();
   private static final BlockState LEAF = ModBlocks.MANGROVE_LEAVES.getDefaultState();
   protected net.minecraftforge.common.IPlantable sapling = (net.minecraftforge.common.IPlantable)ModBlocks.MANGROVE_SAPLING;
   private final int minHeight = 5;
   private boolean roots;
   private int water;
   
   public MangroveTreeFeature(Function<Dynamic<?>, ? extends TreeFeatureConfig> config) {
      super(config);
      
   }

   public boolean place(IWorldGenerationReader worldIn, Random rand, BlockPos position, Set<BlockPos> extra, Set<BlockPos> changedBlocks, MutableBoundingBox box, TreeFeatureConfig config)
   {
	   // Setting the height of the tree (we also set the position to be the first solid block above the ocean floor(Y = 64))
      int height = this.minHeight + rand.nextInt(3);
      position = worldIn.getHeight(Heightmap.Type.OCEAN_FLOOR, position);
      
   // This tests/checks if the tree is able to grow, if not then we exit
 		if (!this.ensureGrowable(worldIn, position, height))
     {
         return false;
     }	
	else
	{
      int x = position.getX();
      int y = position.getY();
      int z = position.getZ();
      
      //Trunk generation
        for (int block = 0; block < height; ++block)
        {
        	LOG = ModBlocks.MANGROVE_LOG.getDefaultState();
        	
        	// If our tree generates in water, we want the trunk to start one block above the water
            BlockPos pos = position.up(block + water);
            placeLogAt(changedBlocks, worldIn, pos, box);
         }
        
        //root generation
        if(roots)
        {
        	// We start growing the root mound from each cardinal direction
            for (Direction direction : Direction.Plane.HORIZONTAL)
            {
            	// We generate our tree's trunk down to where the generation started (ie: where the sapling is grown)
            	for (int i = 0; i <= water; i++)
        	   	{
        	   		placeLogAt(changedBlocks, worldIn, position.down(i), box);
        	   	}
            	
            	// We set the log block to be wood to make orientation simpler (I don't want to deal with knowing if the root is growing out north or east)
            	LOG = ModBlocks.MANGROVE_WOOD.getDefaultState();
            	
            	// We set our position at the base of the trunk (where the top water level is
            	BlockPos pos = position.offset(direction).up(water);
            	
            	// We begin generating our roots from the base of the trunk in the specified cardinal direction
            	createRoots(changedBlocks, worldIn, pos, box, rand, water);
            	
            	// If the tree is extra tall, we want the chance to bring the root mound up by a block, similar to what was shown in the Biome vote video
            	if (rand.nextInt(10) == 1 && height < 7)
            	{
	            	pos = position.offset(direction).up(water);
	            	pos = position.offset(direction).up(water + 1);
	    	   		placeLogAt(changedBlocks, worldIn, pos, box);
            	}
            }
        }
        
        //Leaves generation 
        for (int yPos = y - 3 + (height + water); yPos <= y + (height + water); ++yPos)
        {
        	// We want our leaves to start at the top of the trunk while also accounting for the change due to water
           int leafHeight = yPos - (y + (height + water));
           int leafWidth = 2 - leafHeight / 2;

           // Stereotypical boxy Minecraft tree top
           for(int xPos = x - leafWidth; xPos <= x + leafWidth; ++xPos)
           {
              int xWidth = xPos - x;

              for(int zPos = z - leafWidth; zPos <= z + leafWidth; ++zPos)
              {
                 int zWidth = zPos - z;
                 if (Math.abs(xWidth) != leafWidth || Math.abs(zWidth) != leafWidth || rand.nextInt(2) != 0 && leafHeight != 0)
                 {
                    BlockPos pos = new BlockPos(xPos, yPos, zPos);
                    placeLeafAt(changedBlocks, worldIn, pos, box);
                 }
              }
           }
        }

        //Vine Generation
        for(int yPos = y - 3 + (height + water); yPos <= y + (height + water); ++yPos)
        {
        	// We want to account for the change in height due to water
           int leafHeight = yPos - (y + (height + water));
           int leafWidth = 2 - leafHeight / 2;
           
           // We still want to use mutable blocks since they haven't been placed in the world yet
           BlockPos.Mutable mutable = new BlockPos.Mutable();

           for(int xPos = x - leafWidth; xPos <= x + leafWidth; ++xPos)
           {
              for(int zPos = z - leafWidth; zPos <= z + leafWidth; ++zPos)
              {
            	  mutable.setPos(xPos, yPos, zPos);
                 
                  // If it is a leaf block then we want to place vines on it
                 if (isAirOrLeaves(worldIn, mutable))
                 {
                    BlockPos westBlock = mutable.west();
                    BlockPos eastBlock = mutable.east();
                    BlockPos northBlock = mutable.north();
                    BlockPos southBlock = mutable.south();
                    
                    // We want to place the vines in the correct orientation 
                    if (rand.nextInt(4) == 0 && isAir(worldIn, westBlock))
                    {
                       this.addVine(worldIn, westBlock, VineBlock.EAST);
                    }

                    if (rand.nextInt(4) == 0 && isAir(worldIn, eastBlock)) {
                       this.addVine(worldIn, eastBlock, VineBlock.WEST);
                    }

                    if (rand.nextInt(4) == 0 && isAir(worldIn, northBlock)) {
                       this.addVine(worldIn, northBlock, VineBlock.SOUTH);
                    }

                    if (rand.nextInt(4) == 0 && isAir(worldIn, southBlock)) {
                       this.addVine(worldIn, southBlock, VineBlock.NORTH);
                    }
                 }
              }
           }
        }

        return true;
     }
   }

	// We want our swamp trees to generate vines
   private void addVine(IWorldGenerationReader worldIn, BlockPos pos, BooleanProperty prop) {
      BlockState blockstate = Blocks.VINE.getDefaultState().with(prop, Boolean.valueOf(true));
      this.setBlockState(worldIn, pos, blockstate);
      int i = 4;

      for(BlockPos blockpos = pos.down(); isAir(worldIn, blockpos) && i > 0; --i) {
         this.setBlockState(worldIn, blockpos, blockstate);
         blockpos = blockpos.down();
      }

   }
   
   // This is how I handle root generation
   private void createRoots(Set<BlockPos> changedBlocks, IWorldGenerationReader worldIn, BlockPos pos, MutableBoundingBox box, Random rand, int water)
   {
	   // Even though we start in each cardinal direction, I still want to randomly go in a direction to get a randomized root mound
	   // ( perhaps the roots spread out or become really long instead of compact)
	   Direction direction = Direction.Plane.HORIZONTAL.random(rand);
	   
	   // Until we hit the dirt level, we want to generate roots
		for (int i = 0; i <= water; i++)
	   	{
			// This is what allows our roots to sprawl out from the base
			// I use recursion to recall the same function to make the roots travel down and out
	   		if (rand.nextInt(2) == 1) {createRoots(changedBlocks, worldIn, pos.offset(direction).down(water - i), box, rand, i);}
	   		
	   		// This will just randomly stop the recursion / prevent the root from generating prior to the for loop stopping
	   		if (rand.nextInt(10) == 1) {continue;}
	   		
	   		// This will place our top most root
	   		placeLogAt(changedBlocks, worldIn, pos.down(i), box);
	   		
	   		// If there is water, air, or a grass/seagrass block under our root then we replace all of it with roots until we reach a solid block (like dirt or stone)
	   		int waterBlock = 1;
	   		while (isWater(worldIn, pos.down(i + waterBlock)) || isAir(worldIn, pos.down(i + waterBlock)) || isGrass(worldIn, pos.down(i + waterBlock)))
	   		{
		   		placeLogAt(changedBlocks, worldIn, pos.down(i + waterBlock), box);
		   		waterBlock++;
	   		}
	   	}
   }
   
   // I want to replace short grass and short seagrass blocks
   protected static boolean isGrass(IWorldGenerationBaseReader worldIn, BlockPos pos) {
	      return worldIn.hasBlockState(pos, (state) -> {
	         return state.getBlock() == Blocks.GRASS || state.getBlock() == Blocks.SEAGRASS;
	      });
	   }
   
	// Just as the title says, this sets a log block in the world
   private void placeLogAt(Set<BlockPos> changedBlocks, IWorldGenerationReader worldIn, BlockPos pos, MutableBoundingBox box)
   {
	   if (isAirOrLeaves(worldIn, pos) || isWater(worldIn, pos))
       {
	   		this.placeBlock(worldIn, pos, changedBlocks, box, LOG);
       }
   }
   
	// Just as the title says, this sets a leaf block in the world
   private void placeLeafAt(Set<BlockPos> changedBlocks, IWorldGenerationReader worldIn, BlockPos pos, MutableBoundingBox box)
   {
       if (isAirOrLeaves(worldIn, pos) || isTallPlants(worldIn, pos))
       {
   		this.placeBlock(worldIn, pos, changedBlocks, box, LEAF);
       }
   }
   
   // This is what actually places the (mutable) block into the world. The above functions just get the right block
   protected boolean placeBlock(IWorldGenerationReader worldIn, BlockPos pos, Set<BlockPos> changedBlocks, MutableBoundingBox box, BlockState state)
   {
	      if (!isAirOrLeaves(worldIn, pos) && !isTallPlants(worldIn, pos) && !isWater(worldIn, pos)) {
	         return false;
	      } else {
	         this.func_227217_a_(worldIn, pos, state, box);
	         changedBlocks.add(pos.toImmutable());
	         return true;
	      }
	   }
   
// To test if the tree can grow we first check if there is available space and then if the blocks underneath can sustain our tree
   protected boolean ensureGrowable(IWorldGenerationReader worldIn, BlockPos treePos, int height)
   {
       return this.isSpaceAt(worldIn, treePos, height) && this.ensureViableBlockUnderneath(treePos, worldIn);
   }
   
   // Check if there is space for the tree to grow
 	private boolean isSpaceAt(IWorldGenerationReader worldIn, BlockPos position, int height)
    {
      boolean flag = true;
      roots = false;
  	  water = 0;
      int x = position.getX();
      int y = position.getY();
      int z = position.getZ();
      
	  // Obviously we don't want the tree to grow in the void or above the build limit
      if (y >= 1 && y + height + 1 <= 256)
      {
    	  
    	 // Extra math in case our tree is taller than expected
         for(int yPos = y; yPos <= y + 1 + height; ++yPos)
         {
            int b0 = 1;
            if (yPos == y) {
               b0 = 0;
            }

            if (yPos >= y + 1 + height - 2) {
               b0 = 3;
            }

	        // Use mutable blocks to test if the location is available
            BlockPos.Mutable mutable = new BlockPos.Mutable();

            
            // We check each position for future blocks
            for(int xPos = x - b0; xPos <= x + b0 && flag; ++xPos)
            {
               for(int zPos = z - b0; zPos <= z + b0 && flag; ++zPos)
               {
            	   
            	  // Double check to make sure our tree doesn't generate in the void or above the build limit
                  if (yPos >= 0 && yPos < 256)
                  {
                     mutable.setPos(xPos, yPos, zPos);
                     
                     // We check if our future blocks can be placed in their respective location
                     if (!isAirOrLeaves(worldIn, mutable))
                     {
                    	
                    	// We check if our future blocks are being placed within water
                        if (isWater(worldIn, mutable))
                        {
                        	BlockPos pos = mutable;
                        	
                        	// If our tree generates in water we want it to generate a root mound
                        	roots = true;
                        	int i;
                        	
                        	// We check how many blocks under water our tree starts generating from
                        	 for (i = 1; isWater(worldIn, pos); pos = pos.up())
                             {
                                 i++;
                             }
                        	 
                        	 // We want the total amount of water above our start position
                        	 // If we don't do this then the mutable block will move up and the total amount of water above will decrease
                        	 if (water < i)
                        	 {
                             	water = i;
                        	 }
                        	
                        	 // We don't want our tree to generate too deep from under water
                        	 // (It'd be weird to have a mangrove grow from the ocean floor and grow a giant root mound)
                           if (water > 3) {
                              flag = false;
                           }
                           
                        // The block in the way was not water, leaves, or air
                        } else {
                           flag = false;
                        }
                     }
                  
                  // Guess the tree was somehow either in the void or above the build limit
                  } else {
                     flag = false;
                  }
               }
            }
         }
         return flag;
         
 	  //Tree was either in void or above build limit
      } else {
    	  return false;
      }

    }
 	
 	// Check if the tree can generate on the block underneath
 	private boolean ensureViableBlockUnderneath(BlockPos pos, IWorldGenerationReader worldIn)
    {
 		 // If the block underneath is considered to be a dirt or sand variant then we allow the tree to generate
 		if ((isSoil(worldIn, pos.down(), sapling)))
		{
 			this.setDirtAt(worldIn, pos.down(), pos);
            return true;
		}
 		else
 		{
 			return false;
 		}
    }
}

 

 

Posted

My guess would be that the method that deals with sapling tree growth isn't replacing your sapling because it doesn't know your sapling is a sapling.

Offhand that would make me think that you need to add your sapling to the sapling block tag.

Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable.  If you think this is the case, JUST REPORT ME.  Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice.

 

Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked.

 

DO NOT PM ME WITH PROBLEMS. No help will be given.

Posted (edited)
8 minutes ago, Draco18s said:

My guess would be that the method that deals with sapling tree growth isn't replacing your sapling because it doesn't know your sapling is a sapling.

Offhand that would make me think that you need to add your sapling to the sapling block tag.

I forgot to include my data tags because I thought it was assumed I had them.

data/minecraft/tags/blocks/saplings.json

{
  "replace": false,
  "values": [
    "biome_enhancements:baobab_sapling",
	"biome_enhancements:mangrove_sapling",
    "biome_enhancements:palm_sapling"
  ]
}

 

Edited by killerjdog51

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.

Announcements



×
×
  • Create New...

Important Information

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