Jump to content

killerjdog51

Members
  • Posts

    56
  • Joined

  • Last visited

Everything posted by killerjdog51

  1. There's a discord channel and a forge bot? Honestly I'm not as surprised as I thought I'd be. Guess I'll check it out then. I didn't think it was possible since I didn't see anything past 1.15 on the old website.
  2. Thats what I was thinking, and unfortunately I don't think theres a good way to accomplish that, which is why I was hoping for an easy way to slip my biome layer into the world generation through some sort of forge manager (like the BiomeManager) or through an event (like one of the WorldEvents or WorldTypeEvent). The only other solution I can think of is by generating a feature and inserting my custom biome into the BiomeContainer using reflection, which is possible except I would need to know the exact obfuscated methods/fields (which is like trying to find a needle in a haystack).
  3. Unfortunately that didn't work the way I wanted it to. What I'm trying to accomplish is to have a rare biome that spawns within another more common biome, similar to how bamboo forests spawn within jungles. I've found that if I just add my custom biome to world generation that it will generate on the outside between different biomes (Like in the middle of a jungle, desert, and plains) rather than within/surrounded by a single specific biome. Like its very jarring and unsatisfying, which is why I want to make it generate as a layer instead since I want my biome to behave more akin to bamboo jungles or sunflower plains.
  4. Hey, I was wondering how I could add a custom biome layer to vanilla world generation? I see in IForgeWorldType there is a getBiomeLayer method that takes an IAreaFactory, overworldGenSettings, and a LongFunction (world seed) and returns the same IAreaFactory with bamboo forests. I was thinking I'd use the WorldTypeEvent.BiomeSize event to seamlessly insert my custom biome layer into vanilla generation since the event occurs within the function that builds the vanilla biome layers. Because my understanding is that if I got the vanilla IAreaFactory I would have to return it with my custom biome layer before LayerUtil gives the layer to the OverworldBiomeProvider. The issue is I can't figure out how to get the vanilla IAreaFactory, so I was wondering if there was a better way to add a custom biome layer to vanilla world generation?
  5. Thanks, it worked! I'm an idiot for not realizing that biomes were dynamic and being called within the BiomeContainer with its own version of a dynamic Biome Registry. All I had to do was grab copy the reference to that and get a reference to my biome and the dynamic biome id that way.
  6. I've narrowed things down to: public int getInt(final Object k) { if ((strategy.equals(((K) k), (null)))) return containsNullKey ? value[n] : defRetValue; K curr; final K[] key = this.key; int pos; // The starting point. if (((curr = key[pos = (it.unimi.dsi.fastutil.HashCommon.mix(strategy.hashCode((K) k))) & mask]) == null)) return defRetValue; if ((strategy.equals((K) (k), (curr)))) return value[pos]; // There's always an unused entry. while (true) { if (((curr = key[pos = (pos + 1) & mask]) == null)) return defRetValue; if ((strategy.equals((K) (k), (curr)))) return value[pos]; } } Somehow the key[pos = (it.unimi.dsi.fastutil.HashCommon.mix(strategy.hashCode((K) k))) & mask] is used to calculate the biome data position within the list K. For some reason it seems like my inserted biome returns the wrong position value (though it is within the K list). I've tested it with Minecraft biomes like forest and mushroom fields, they also don't return the correct position unless its natural generation (not inserted like mine). Does anyone have ideas on why this is? If I had to guess inserting the biome data generates a different hash, but I don't understand why. perhaps its caused by ForgeRegistries.BIOMES.getValue()?
  7. So I have an Oasis feature that generates and edits the biome around it. The feature generation part works well, the issue is that it no longer edits the biome like it did in previous versions (1.12, 1.14, & 1.15). Or it does, but instead of my custom biome it generates the "Received invalid biome id: -1" error and uses the Minecraft:Ocean biome. The biome spawns perfectly fine if I generate it using the single biome world type. OasisBiome
  8. Thanks, I switched it to an iterator and it worked. I was able to remove the features after the game had loaded but before they generated.
  9. How did you remove/prevent the generation? Because when I use f.remove() I get a ConcurrentModificationException.
  10. Thanks, it worked! I generally try to avoid reflection because I don't want to accidently break the game... I was hoping there would be a better way to implement trees, but considering the constructor itself is private, I guess there's not many options available. I kinda wish there was a consistent tutorial for trees, but I suppose that'd be hard since mojang keeps changing things between updates. The mod I'm working on is for 1.14, 1.15, and now 1.16, and each update has different code for trees, like they keep getting more and more modularized/customizable. Which certainly isn't a bad thing.
  11. Even though the registry has? public static final Registry<TrunkPlacerType<?>> TRUNK_REPLACER = createRegistry(TRUNK_PLACER_TYPE_KEY, () -> { return TrunkPlacerType.STRAIGHT_TRUNK_PLACER; });
  12. I'm trying to add custom trees to my mod and to do that I need a custom Trunk. The only way to do this is to register my own type using: private static <P extends AbstractTrunkPlacer> TrunkPlacerType<P> register(String key, Codec<P> type) { return Registry.register(Registry.TRUNK_REPLACER, key, new TrunkPlacerType<>(type)); } The issue though is that it errors on the new TrunkPlacerType<>(type) saying "Cannot infer type arguments for TrunkPlacerType<>". And if I delete TrunkPlacerType<> I get an error on Register.register saying "The method register(Registry<V>, ResourceLocation, T) in the type Registry is not applicable for the arguments (Registry<TrunkPlacerType<?>>, ResourceLocation, Codec<P>)". My next thought process was to add my Trunk Placer Type to a forge deferred register, except that it doesn't exist. There's a Forge registry for FoliagePlacerType and TreeDecorationType, but not one for TrunkPlacerType. So I'm wondering if anyone has an idea that I haven't thought of? Like is there a way for me to add my trunk type to the minecraft registry? Or is there a way to create a custom Forge registry/deferred register?
  13. I've updated my 1.14.4 mod to 1.15.2 and it turns out sign rendering has been somewhat improved/changed. So I changed from using a direct resource path to the atlas. Good thing is that the sign render for the block works, problem is that the sign render for the sign edit screen doesn't because that uses packets to communicate from server to client and I just don't understand that stuff. So I'm asking for help to get my sign edit screen working, below are images of the issue along with my code. Thank you in advanced. Sign edit screen: Sign: Wood Types: package biome_enhancments.util; import java.util.Set; import it.unimi.dsi.fastutil.objects.ObjectArraySet; import net.minecraft.block.WoodType; public class ModWoodTypes extends WoodType { private static final Set<WoodType> VALUES = new ObjectArraySet<>(); public static final WoodType BAOBAB = register(new ModWoodTypes("baobab")); public static final WoodType MANGROVE = register(new ModWoodTypes("mangrove")); public static final WoodType PALM = register(new ModWoodTypes("palm")); private final String name; protected ModWoodTypes(String nameIn) { super(nameIn); this.name = nameIn; } private static WoodType register(WoodType woodTypeIn) { VALUES.add(woodTypeIn); return woodTypeIn; } public String getName() { return this.name; } } Sign Item: public static final Item BAOBAB_SIGN = new ModSignItem(new Item.Properties().maxStackSize(16).group(ItemGroup.DECORATIONS), ModBlocks.BAOBAB_SIGN, ModBlocks.BAOBAB_WALL_SIGN); public static final Item MANGROVE_SIGN = new ModSignItem(new Item.Properties().maxStackSize(16).group(ItemGroup.DECORATIONS), ModBlocks.MANGROVE_SIGN, ModBlocks.MANGROVE_WALL_SIGN); public static final Item PALM_SIGN = new ModSignItem(new Item.Properties().maxStackSize(16).group(ItemGroup.DECORATIONS), ModBlocks.PALM_SIGN, ModBlocks.PALM_WALL_SIGN); package biome_enhancments.items; import javax.annotation.Nullable; import biome_enhancments.tileentity.ModSignTileEntity; import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; import net.minecraft.item.SignItem; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; public class ModSignItem extends SignItem{ public ModSignItem(Properties propertiesIn, Block floorBlockIn, Block wallBlockIn) { super(propertiesIn, floorBlockIn, wallBlockIn); } @Override protected boolean onBlockPlaced(BlockPos pos, World worldIn, @Nullable PlayerEntity player, ItemStack stack, BlockState state) { boolean flag = super.onBlockPlaced(pos, worldIn, player, stack, state); if (!worldIn.isRemote && !flag && player != null) { player.openSignEditor((ModSignTileEntity)worldIn.getTileEntity(pos)); } return flag; } } Sign Block: public static final Block BAOBAB_SIGN = new ModStandingSignBlock(Block.Properties.create(Material.WOOD, MaterialColor.ADOBE).doesNotBlockMovement().hardnessAndResistance(1.0F).sound(SoundType.WOOD), ModWoodTypes.BAOBAB); public static final Block BAOBAB_WALL_SIGN = new ModWallSignBlock(Block.Properties.create(Material.WOOD, MaterialColor.ADOBE).doesNotBlockMovement().hardnessAndResistance(1.0F).sound(SoundType.WOOD).lootFrom(BAOBAB_SIGN), ModWoodTypes.BAOBAB); public static final Block MANGROVE_SIGN = new ModStandingSignBlock(Block.Properties.create(Material.WOOD, MaterialColor.OBSIDIAN).doesNotBlockMovement().hardnessAndResistance(1.0F).sound(SoundType.WOOD), ModWoodTypes.MANGROVE); public static final Block MANGROVE_WALL_SIGN = new ModWallSignBlock(Block.Properties.create(Material.WOOD, MaterialColor.OBSIDIAN).doesNotBlockMovement().hardnessAndResistance(1.0F).sound(SoundType.WOOD).lootFrom(MANGROVE_SIGN), ModWoodTypes.MANGROVE); public static final Block PALM_SIGN = new ModStandingSignBlock(Block.Properties.create(Material.WOOD, MaterialColor.SAND).doesNotBlockMovement().hardnessAndResistance(1.0F).sound(SoundType.WOOD), ModWoodTypes.PALM); public static final Block PALM_WALL_SIGN = new ModWallSignBlock(Block.Properties.create(Material.WOOD, MaterialColor.SAND).doesNotBlockMovement().hardnessAndResistance(1.0F).sound(SoundType.WOOD).lootFrom(PALM_SIGN), ModWoodTypes.PALM); package biome_enhancments.blocks; import biome_enhancments.tileentity.ModSignTileEntity; import net.minecraft.block.StandingSignBlock; import net.minecraft.block.WoodType; import net.minecraft.tileentity.TileEntity; import net.minecraft.world.IBlockReader; public class ModStandingSignBlock extends StandingSignBlock { public ModStandingSignBlock(Properties properties, WoodType woodType) { super(properties, woodType); } @Override public TileEntity createNewTileEntity(IBlockReader worldIn) { return new ModSignTileEntity(); } } package biome_enhancments.blocks; import biome_enhancments.tileentity.ModSignTileEntity; import net.minecraft.block.WallSignBlock; import net.minecraft.block.WoodType; import net.minecraft.tileentity.TileEntity; import net.minecraft.world.IBlockReader; public class ModWallSignBlock extends WallSignBlock { public ModWallSignBlock(Properties properties, WoodType woodType) { super(properties, woodType); } @Override public TileEntity createNewTileEntity(IBlockReader worldIn) { return new ModSignTileEntity(); } } Sign Tile Entity: public static final TileEntityType<ModSignTileEntity> SIGN = TileEntityType.Builder.create(ModSignTileEntity::new , ModBlocks.BAOBAB_SIGN, ModBlocks.BAOBAB_WALL_SIGN, ModBlocks.MANGROVE_SIGN, ModBlocks.MANGROVE_WALL_SIGN, ModBlocks.PALM_SIGN, ModBlocks.PALM_WALL_SIGN).build(null); package biome_enhancments.tileentity; import biome_enhancments.init.ModTileEntityTypes; import net.minecraft.tileentity.SignTileEntity; import net.minecraft.tileentity.TileEntityType; public class ModSignTileEntity extends SignTileEntity { @Override public TileEntityType<?> getType() { return ModTileEntityTypes.SIGN; } } Sign Render: package biome_enhancments.tileentity; import java.util.List; import com.mojang.blaze3d.matrix.MatrixStack; import com.mojang.blaze3d.platform.GlStateManager; import com.mojang.blaze3d.vertex.IVertexBuilder; import biome_enhancments.BiomeEnhancements; import biome_enhancments.blocks.ModStandingSignBlock; import biome_enhancments.blocks.ModWallSignBlock; import biome_enhancments.init.ModBlocks; import net.minecraft.block.AbstractSignBlock; import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.block.StandingSignBlock; import net.minecraft.block.WallSignBlock; import net.minecraft.block.WoodType; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.AbstractGui; import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.RenderComponentsUtil; import net.minecraft.client.renderer.Atlases; import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.client.renderer.IRenderTypeBuffer; import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.Vector3f; import net.minecraft.client.renderer.model.Material; import net.minecraft.client.renderer.texture.NativeImage; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.renderer.tileentity.SignTileEntityRenderer; import net.minecraft.client.renderer.tileentity.TileEntityRenderer; import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher; import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.tileentity.SignTileEntity; import net.minecraft.util.ResourceLocation; import net.minecraft.util.text.ITextComponent; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; @OnlyIn(Dist.CLIENT) public class ModSignTileEntityRenderer extends TileEntityRenderer<ModSignTileEntity> { private final SignTileEntityRenderer.SignModel model = new SignTileEntityRenderer.SignModel(); public ModSignTileEntityRenderer(TileEntityRendererDispatcher rendererDispatcherIn) { super(rendererDispatcherIn); } public void render(ModSignTileEntity tileEntityIn, float partialTicks, MatrixStack matrixStackIn, IRenderTypeBuffer bufferIn, int combinedLightIn, int combinedOverlayIn) { BlockState blockstate = tileEntityIn.getBlockState(); matrixStackIn.push(); float f = 0.6666667F; if (blockstate.getBlock() instanceof StandingSignBlock) { matrixStackIn.translate(0.5D, 0.5D, 0.5D); float f1 = -((float)(blockstate.get(StandingSignBlock.ROTATION) * 360) / 16.0F); matrixStackIn.rotate(Vector3f.YP.rotationDegrees(f1)); this.model.signStick.showModel = true; } else { matrixStackIn.translate(0.5D, 0.5D, 0.5D); float f4 = -blockstate.get(WallSignBlock.FACING).getHorizontalAngle(); matrixStackIn.rotate(Vector3f.YP.rotationDegrees(f4)); matrixStackIn.translate(0.0D, -0.3125D, -0.4375D); this.model.signStick.showModel = false; } matrixStackIn.push(); matrixStackIn.scale(0.6666667F, -0.6666667F, -0.6666667F); Material material = getMaterial(blockstate.getBlock()); IVertexBuilder ivertexbuilder = material.getBuffer(bufferIn, this.model::getRenderType); this.model.signBoard.render(matrixStackIn, ivertexbuilder, combinedLightIn, combinedOverlayIn); this.model.signStick.render(matrixStackIn, ivertexbuilder, combinedLightIn, combinedOverlayIn); matrixStackIn.pop(); FontRenderer fontrenderer = this.renderDispatcher.getFontRenderer(); float f2 = 0.010416667F; matrixStackIn.translate(0.0D, (double)0.33333334F, (double)0.046666667F); matrixStackIn.scale(0.010416667F, -0.010416667F, 0.010416667F); int i = tileEntityIn.getTextColor().getTextColor(); double d0 = 0.4D; int j = (int)((double)NativeImage.getRed(i) * 0.4D); int k = (int)((double)NativeImage.getGreen(i) * 0.4D); int l = (int)((double)NativeImage.getBlue(i) * 0.4D); int i1 = NativeImage.getCombined(0, l, k, j); for(int j1 = 0; j1 < 4; ++j1) { String s = tileEntityIn.getRenderText(j1, (p_212491_1_) -> { List<ITextComponent> list = RenderComponentsUtil.splitText(p_212491_1_, 90, fontrenderer, false, true); return list.isEmpty() ? "" : list.get(0).getFormattedText(); }); if (s != null) { float f3 = (float)(-fontrenderer.getStringWidth(s) / 2); fontrenderer.renderString(s, f3, (float)(j1 * 10 - tileEntityIn.signText.length * 5), i1, false, matrixStackIn.getLast().getMatrix(), bufferIn, false, 0, combinedLightIn); } } matrixStackIn.pop(); } public static Material getMaterial(Block blockIn) { WoodType woodtype; if (blockIn instanceof AbstractSignBlock) { woodtype = ((AbstractSignBlock)blockIn).getWoodType(); } else { woodtype = WoodType.OAK; } return getSignMaterial(woodtype); } public static Material getSignMaterial(WoodType woodtype) { return new Material(Atlases.SIGN_ATLAS, new ResourceLocation(BiomeEnhancements.MOD_ID, "entity/signs/" + woodtype.getName())); } } package biome_enhancments.tileentity; import biome_enhancments.BiomeEnhancements; import net.minecraft.client.renderer.Atlases; import net.minecraft.client.renderer.texture.AtlasTexture; import net.minecraft.util.ResourceLocation; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.client.event.TextureStitchEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; @Mod.EventBusSubscriber(modid = BiomeEnhancements.MOD_ID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD) public class ModSignTextureStitch { public static final ResourceLocation BAOBAB = new ResourceLocation(BiomeEnhancements.MOD_ID, "entity/signs/baobab"); public static final ResourceLocation MANGROVE = new ResourceLocation(BiomeEnhancements.MOD_ID, "entity/signs/mangrove"); public static final ResourceLocation PALM = new ResourceLocation(BiomeEnhancements.MOD_ID, "entity/signs/palm"); @SubscribeEvent public static void onStitchEvent(TextureStitchEvent.Pre event) { ResourceLocation stitching = event.getMap().getTextureLocation(); if(!stitching.equals(Atlases.SIGN_ATLAS)) { return; } boolean added = event.addSprite(BAOBAB); added = event.addSprite(MANGROVE); added = event.addSprite(PALM); } }
  14. Like this? @Mod.EventBusSubscriber(modid = BiomeEnhancements.MOD_ID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD) public class ModSignTextureStitch { public static final ResourceLocation BAOBAB = new ResourceLocation(BiomeEnhancements.MOD_ID, "entity/signs/baobab"); public static final ResourceLocation MANGROVE = new ResourceLocation(BiomeEnhancements.MOD_ID, "entity/signs/mangrove"); public static final ResourceLocation PALM = new ResourceLocation(BiomeEnhancements.MOD_ID, "entity/signs/palm"); @SubscribeEvent public static void onStitchEvent(TextureStitchEvent.Pre event) { ResourceLocation stitching = event.getMap().getTextureLocation(); if(!stitching.equals(Atlases.SIGN_ATLAS)) { return; } boolean added = event.addSprite(BAOBAB); added = event.addSprite(MANGROVE); added = event.addSprite(PALM); } } The signs have texture, but when you "type" in them the visual is the missing texture thing. Here's how I load the textures in the sign renderer. public static Material getMaterial(Block blockIn) { WoodType woodtype; if (blockIn instanceof AbstractSignBlock) { woodtype = ((AbstractSignBlock)blockIn).getWoodType(); } else { woodtype = WoodType.OAK; } return getSignMaterial(woodtype); } public static Material getSignMaterial(WoodType woodtype) { return new Material(Atlases.SIGN_ATLAS, new ResourceLocation(BiomeEnhancements.MOD_ID, "entity/signs/" + woodtype.getName())); } }
  15. How did you add to the sign atlas? I'm experiencing the same issue.
  16. 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" ] }
  17. 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; } } }
  18. Hey, I was wondering if there is a way to add an edge to a custom biome using Forge. Like have a biome spawn surrounded by other biomes. My idea is like having a mountain peak biome spawn with mountains surrounding it. I'm able to do this in Fabric and am trying to port it to Forge.
  19. Thank you, I got it working and I really appreciate your help. I can't believe how stupid I was though, I checked the sign blocks to see if there was anything related to tile entities, didn't even notice that they were extending an abstract sign class. This whole time I was looking for how the Item class called the tile entity. The last thing I need then is texturing my sign blocks, Now that I can actually place it in the world I see that they all have the oak texture instead of my custom sign textures (placed in assets/Mod_ID/textures/entity/signs folder) Edit: figured out the texture thing, just had to make a TileEntityRenderer class.
  20. Can you post your solution? I'm lost on how I'm supposed to pass my custom SignTileEntity to the SignItem class. I have my custom SignTileEntity extend Minecraft's SignTileEntity and override the TileEntity::GetType so that it returns my custom SignTileEntityType. But my custom SignTileEntity::GetType is never called. When I place my custom SignItem into the world it calls world.getTileEntity which somehow returns Minecraft's SignTileEntity (I tried following it in the debugger and it wouldn't reveal where it gets the tile entity from).
  21. Thought I'd give an update. I got my idea working, but through somewhat unconventional means. I tried getting the structure to work with no avail. Perhaps in the future I'll revisit how structures work when I have a more uniform structure (like the witch hut, desert pyramid, or jungle temple; something that'll always spawn the same way), but for this I decided to stick with the oasis being a feature. Spawning is pretty much the same, the only difference is that I placed my custom sapling and then forced it to grow into a tree. I also randomly placed reeds, grass, and flowers around the lake. Then I had the function recursively call itself to try placing a second oasis within like 5 blocks or so away from the center of the already generated one. This allows my feature to appear somewhat random in shape and size. I don't know if what I did was the best way, but I think it works out quite well and blends somewhat naturally in the Minecraft environment. I think it's also simpler to understand and get working compared to structures. If I figure out a way to allow my feature to work as a structure though I may consider switching to that though.
  22. Is this on the right track of how to generate a structure? It doesn't seem to be spawning in the game. public class OasisStructure extends ScatteredStructure<NoFeatureConfig> { private static final int seedModifier = 2521523; private static final Random random = new Random(seedModifier); public OasisStructure(Function<Dynamic<?>, ? extends NoFeatureConfig> config) { super(config); } @Override public String getStructureName() { return "Oasis"; } @Override public int getSize() { return 1; } protected int getDistance() { return 20; } protected int getSeparation() { return 11; } @Override protected int getSeedModifier() { return seedModifier; } @Override protected ChunkPos getStartPositionForPosition(ChunkGenerator<?> chunkGenerator, Random random, int x, int z, int spacingOffsetsX, int spacingOffsetsZ) { random.setSeed(this.getSeedModifier()); int distance = this.getDistance(); int separation = this.getSeparation(); int x1 = x + distance * spacingOffsetsX; int z1 = z + distance * spacingOffsetsZ; int x2 = x1 < 0 ? x1 - distance + 1 : x1; int z2 = z1 < 0 ? z1 - distance + 1 : z1; int x3 = x2 / distance; int z3 = z2 / distance; ((SharedSeedRandom) random).setLargeFeatureSeedWithSalt(chunkGenerator.getSeed(), x3, z3, this.getSeedModifier()); x3 = x3 * distance; z3 = z3 * distance; x3 = x3 + random.nextInt(distance - separation); z3 = z3 + random.nextInt(distance - separation); return new ChunkPos(x3, z3); } private static int getYPosForStructure(int chunkX, int chunkZ, ChunkGenerator<?> generatorIn) { Random random = new Random((long)(chunkX + chunkZ * 10387313)); Rotation rotation = Rotation.values()[random.nextInt(Rotation.values().length)]; int x = 5; int z = 5; if (rotation == Rotation.CLOCKWISE_90) { x = -5; } else if (rotation == Rotation.CLOCKWISE_180) { x = -5; z = -5; } else if (rotation == Rotation.COUNTERCLOCKWISE_90) { z = -5; } int xPos = (chunkX << 4) + 7; int zPos = (chunkZ << 4) + 7; int pos1 = generatorIn.func_222531_c(xPos, zPos, Heightmap.Type.WORLD_SURFACE_WG); int pos2 = generatorIn.func_222531_c(xPos, zPos + z, Heightmap.Type.WORLD_SURFACE_WG); int pos3 = generatorIn.func_222531_c(xPos + x, zPos, Heightmap.Type.WORLD_SURFACE_WG); int pos4 = generatorIn.func_222531_c(xPos + x, zPos + z, Heightmap.Type.WORLD_SURFACE_WG); return Math.min(Math.min(pos1, pos2), Math.min(pos3, pos4)); } @Override public IStartFactory getStartFactory() { return OasisStructure.Start::new; } public static class Start extends StructureStart { public Start(Structure<?> structure, int chunkX, int chunkZ, Biome biome, MutableBoundingBox structureBoundingBox, int reference, long seed) { super(structure, chunkX, chunkZ, biome, structureBoundingBox, reference, seed); } public void init(ChunkGenerator<?> generator, TemplateManager templateManagerIn, int chunkX, int chunkZ, Biome biomeIn) { int worldX = chunkX * 16; int worldZ = chunkZ * 16; int worldY = OasisStructure.getYPosForStructure(chunkX, chunkZ, generator); BlockPos blockpos = new BlockPos(worldX + random.nextInt(15), worldY, worldZ + random.nextInt(15)); OasisStructurePiece oasispiece = new OasisStructurePiece(random, blockpos, 1); this.components.add(oasispiece); this.recalculateStructureSize(); } } } public class OasisStructurePiece extends ScatteredStructurePiece { protected OasisStructurePiece(Random rand, BlockPos pos, int size) { super(StructurePieceRegistry.OASIS_PIECE, rand, pos.getX(), pos.getY(), pos.getZ(), (11*size), -(5*size), (11*size)); } protected OasisStructurePiece(TemplateManager template, CompoundNBT nbt) { super(StructurePieceRegistry.OASIS_PIECE, nbt); } @Override public boolean addComponentParts(IWorld worldIn, Random randomIn, MutableBoundingBox structureBoundingBoxIn, ChunkPos p_74875_4_) { this.fillWithBlocks(worldIn, structureBoundingBoxIn, 1, 1, 1, 10, 1, 10, Blocks.GRASS_BLOCK.getDefaultState(), Blocks.WATER.getDefaultState(), false); this.fillWithBlocks(worldIn, structureBoundingBoxIn, 1, 1, 1, 10, 1, 10, Blocks.DIRT.getDefaultState(), Blocks.WATER.getDefaultState(), false); this.fillWithBlocks(worldIn, structureBoundingBoxIn, 1, 1, 1, 9, 1, 9, Blocks.DIRT.getDefaultState(), Blocks.WATER.getDefaultState(), false); this.fillWithBlocks(worldIn, structureBoundingBoxIn, 1, 1, 1, 8, 1, 8, Blocks.DIRT.getDefaultState(), Blocks.WATER.getDefaultState(), false); this.fillWithBlocks(worldIn, structureBoundingBoxIn, 1, 1, 1, 7, 1, 7, Blocks.DIRT.getDefaultState(), Blocks.WATER.getDefaultState(), false); return true; } } public class WorldFeatures { public static AbstractTreeFeature<NoFeatureConfig> PALM_TREE = new PalmTreeFeature(NoFeatureConfig::deserialize); public static Structure<NoFeatureConfig> OASIS = new OasisStructure(NoFeatureConfig::deserialize); } public class StructureRegistry { public static final Structure<?> OASIS = register("oasis", WorldFeatures.OASIS); private static Structure<?> register(String key, Structure<?> type) { return Registry.register(Registry.STRUCTURE_FEATURE, new ResourceLocation(Main.MOD_ID, key), type); } } public class StructurePieceRegistry { public static final IStructurePieceType OASIS_PIECE = register("oasis", OasisStructurePiece::new); private static IStructurePieceType register(String key, IStructurePieceType type) { return Registry.register(Registry.STRUCTURE_PIECE, new ResourceLocation(Main.MOD_ID, key), type); } } public class WorldGeneration { public static Biome OASIS = new OasisBiome(); public static void registerBiome(Biome biome, int weight, BiomeType type, Type... types) { BiomeDictionary.addTypes(biome, types); BiomeManager.addBiome(type, new BiomeEntry(biome, weight)); BiomeManager.addSpawnBiome(biome); //BiomeProvider.BIOMES_TO_SPAWN_IN.add(biome); } public static void init() { Biomes.BEACH.addFeature( Decoration.VEGETAL_DECORATION, Biome.createDecoratedFeature( WorldFeatures.PALM_TREE, IFeatureConfig.NO_FEATURE_CONFIG, Placement.COUNT_EXTRA_HEIGHTMAP, new AtSurfaceWithExtraConfig(0, 0.06F, 1)) ); Biomes.DESERT.addStructure(WorldFeatures.OASIS, IFeatureConfig.NO_FEATURE_CONFIG); /** Biomes.DESERT.addFeature( Decoration.LOCAL_MODIFICATIONS, Biome.createDecoratedFeature( WorldFeatures.OASIS, IFeatureConfig.NO_FEATURE_CONFIG, Placement.WATER_LAKE, new LakeChanceConfig(1)) ); **/ } } I'm sorry, I'm not familiar with how any of this stuff works.
  23. I have no intention of placing individual blocks. However I am unfamiliar with structures, I've been looking at the different structures within Minecraft as well as the Scattered Structure and Structure classes, It would be useful to know what exactly getSeedModifier and getSize represent/do? Like does getSize return the amount of chunks the structure is allowed to spawn in? or is it just a relative size amount and not actually calculated? I understand how to build the structure, its just these two functions that I'd like to know the reason for including. Also, does the structure automatically spawn on the top surface block or do I need to calculate where to spawn similar to finding the Y value in my feature class? Perhaps all of this is trivial, but I'd like to have a basic understanding before beginning (especially since my structure will have varying sizes based on how many pools of water it spawns). And why would I need an nbt resource location, what is that used for in the generation of structures? I just want to fully understand what I'm getting into since I haven't been able to find structure tutorials for 1.14.
  24. Upon further research I believe you have the right Idea. I don't know how exactly it will work yet, But I'll try combining the two features that I mentioned above (the Oasis and Palm Tree) into a structure that will randomly generate. Then the lake could perhaps be a little random instead of the cookie-cutter shape I currently have. Like randomly choose a x amount of central positions within the structure for where to spawn the oasis feature. And then choose a series of random positions that have grass underneath for where to spawn the palm tree feature. I don't know if this will work, But I believe it is the best step forward for creating a unique and naturally looking Oasis.
  25. Would it be necessary to create a structure? I'll certainly look into how structure generation works, however my goal is to simply create a lake in the desert with vegetation (grass, flowers, and trees) spawning around it on the grass. Here's what I have so far, I generate a lake of water with dirt and grass surrounding it and then I set the chunk's biome to my custom Oasis biome. I was hoping that by doing that my tree features would just generate, but unfortunately that's not the case. At least it causes the Grass to be green though, which was the main point of the biome. package killerjdog51.biomeEnhancementsMod.world.gen.feature; import java.util.Random; import java.util.Set; import java.util.function.Function; import com.mojang.datafixers.Dynamic; import killerjdog51.biomeEnhancementsMod.init.WorldGeneration; import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.block.Blocks; import net.minecraft.block.material.Material; import net.minecraft.tags.BlockTags; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.ChunkPos; import net.minecraft.util.math.MutableBoundingBox; import net.minecraft.world.IWorld; import net.minecraft.world.LightType; import net.minecraft.world.biome.Biome; import net.minecraft.world.biome.Biomes; import net.minecraft.world.chunk.ChunkStatus; import net.minecraft.world.gen.ChunkGenerator; import net.minecraft.world.gen.GenerationSettings; import net.minecraft.world.gen.IWorldGenerationReader; import net.minecraft.world.gen.feature.Feature; import net.minecraft.world.gen.feature.NoFeatureConfig; public class OasisFeature extends Feature<NoFeatureConfig> { private static final BlockState AIR = Blocks.AIR.getDefaultState(); private static final BlockState WATER = Blocks.WATER.getDefaultState(); private static final BlockState GRASS = Blocks.GRASS_BLOCK.getDefaultState(); private static final BlockState DIRT = Blocks.Dirt.getDefaultState(); private Static final BlockState SAND = Blocks.Sand.getDefaultState(); private int size; public OasisFeature(Function<Dynamic<?>, ? extends NoFeatureConfig> configFactoryIn) { super(configFactoryIn); } @Override public boolean place(IWorld worldIn, ChunkGenerator<? extends GenerationSettings> generator, Random rand, BlockPos pos, NoFeatureConfig config) { int x = pos.getX(); int z = pos.getZ(); size = 1 + rand.nextInt(3); while(pos.getY() > 65 && worldIn.isAirBlock(pos)) { pos = pos.down(); } while(!(worldIn.getLightFor(LightType.SKY, pos) > 0)) { pos = pos.up(); } if (pos.getY() <= 65) { return false; } else { ChunkPos chunkpos = new ChunkPos(pos); if (!worldIn.getChunk(chunkpos.x, chunkpos.z, ChunkStatus.STRUCTURE_REFERENCES).getStructureReferences(Feature.VILLAGE.getStructureName()).isEmpty()) { return false; } for (int xPos = -(11*size); xPos <= (12*size); ++xPos) { for (int zPos = -(11*size); zPos <= (12*size); ++zPos) { for (int yPos = -(8*size); yPos <0; ++yPos) { if (xPos * xPos + zPos * zPos >= (11*(size*size)) && !worldIn.getBlockState(pos.add(xPos, yPos, zPos)).isSolid()) { return false; } } } } Layer(worldIn, pos, (11*size), SAND); Layer(worldIn, pos, (10*size), GRASS); for (int depth = 1; depth <= (4*size); depth++) { Layer(worldIn, pos.down(depth), (10*size), DIRT); } for (int depth = 0; depth < (3*size); depth++) { Layer(worldIn, pos.down(depth), ((8-depth)*size), WATER); } for (int air = 1; air <= 4; air++) { Layer(worldIn, pos.up(air), (10*size), AIR); } return true; } } private void Layer(IWorld worldIn, BlockPos layerCenter, int width, BlockState state) { int max = width * width; for (int x = -width; x <= width; ++x) { for (int z = -width; z <= width; ++z) { if (x * x + z * z <= max) { BlockPos blockpos = layerCenter.add(x, 0, z); worldIn.setBlockState(blockpos, state, 2); worldIn.getChunk(blockpos).getBiomes()[(blockpos.getX() & 15) << 4 | (blockpos.getZ() & 15)] = WorldGeneration.OASIS; } } } } } public class WorldFeatures { public static AbstractTreeFeature<NoFeatureConfig> PALM_TREE = new PalmTreeFeature(NoFeatureConfig::deserialize); public static Feature<NoFeatureConfig> OASIS = new OasisFeature(NoFeatureConfig::deserialize); } I want to trigger the palm tree feature to generate after I've created the lake portion.
×
×
  • Create New...

Important Information

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