That's one heck of a commit you've got there...

I may have got a bit behind with version control. A lot behind, in fact. Maybe I'll go back and split this sometime - then again, I probably won't. But hey, at least it's here!
This commit is contained in:
Electroblob77
2019-08-18 00:04:18 +01:00
parent 2680d02304
commit f37812be3e
1564 changed files with 47652 additions and 12984 deletions
@@ -0,0 +1,51 @@
package electroblob.wizardry.worldgen;
import net.minecraft.block.BlockStoneBrick;
import net.minecraft.init.Blocks;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraft.world.gen.structure.template.ITemplateProcessor;
import net.minecraft.world.gen.structure.template.Template;
import javax.annotation.Nullable;
/** Structure template processor that randomly 'mossifies' cobblestone and stone bricks in the structure. This is done
* using weighting so there is more moss at the bottom, making it look more natural. */
// Behold, the ACME Mossifier 3000! (Patent pending)
public class MossifierTemplateProcessor implements ITemplateProcessor {
private final float mossiness;
private final float heightWeight;
private final int groundLevel;
/**
* Creates a new {@code MossifierTemplateProcessor} with the given parameters.
* @param mossiness The chance for each block in a given layer to be mossified.
* @param heightWeight The amount by which mossiness reduces for each subsequent level upwards.
* @param groundLevel The ground level for the structure, at which height is taken to be zero for the purposes of
* calculating mossiness.
*/
public MossifierTemplateProcessor(float mossiness, float heightWeight, int groundLevel){
this.mossiness = mossiness;
this.heightWeight = heightWeight;
this.groundLevel = groundLevel;
}
@Nullable
@Override
public Template.BlockInfo processBlock(World world, BlockPos pos, Template.BlockInfo info){
float chance = mossiness - heightWeight * (pos.getY() - groundLevel);
if(world.rand.nextFloat() < chance){
if(info.blockState.getBlock() == Blocks.COBBLESTONE){
return new Template.BlockInfo(info.pos, Blocks.MOSSY_COBBLESTONE.getDefaultState(), info.tileentityData);
}else if(info.blockState.getBlock() == Blocks.STONEBRICK){
return new Template.BlockInfo(info.pos, Blocks.STONEBRICK.getDefaultState()
.withProperty(BlockStoneBrick.VARIANT, BlockStoneBrick.EnumType.MOSSY), info.tileentityData);
}
}
return info;
}
}
@@ -0,0 +1,39 @@
package electroblob.wizardry.worldgen;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraft.world.gen.structure.template.ITemplateProcessor;
import net.minecraft.world.gen.structure.template.Template;
import javax.annotation.Nullable;
/** Structure template processor that allows multiple processors to be run in order. */
public class MultiTemplateProcessor implements ITemplateProcessor {
private final ITemplateProcessor[] processors;
private final boolean stopWhenNull;
/**
* Creates a new {@code MultiTemplateProcessor} which applies the given processors in order.
* @param stopWhenNull True to skip any remaining processors in the sequence if one of them returns null, false to
* process them all regardless. If this is false, you should ensure all the given processors
* accept null {@link net.minecraft.world.gen.structure.template.Template.BlockInfo} arguments.
* @param processors The processors to be run, in order (i.e. the first one given will be applied first).
*/
public MultiTemplateProcessor(boolean stopWhenNull, ITemplateProcessor... processors){
this.processors = processors;
this.stopWhenNull = stopWhenNull;
}
@Nullable
@Override
public Template.BlockInfo processBlock(World world, BlockPos pos, Template.BlockInfo info){
for(ITemplateProcessor processor : processors){
info = processor.processBlock(world, pos, info);
if(stopWhenNull && info == null) break;
}
return info;
}
}
@@ -0,0 +1,90 @@
package electroblob.wizardry.worldgen;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.block.Block;
import net.minecraft.block.BlockPlanks;
import net.minecraft.block.BlockWoodSlab;
import net.minecraft.init.Blocks;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraft.world.gen.structure.template.ITemplateProcessor;
import net.minecraft.world.gen.structure.template.Template;
import javax.annotation.Nullable;
import java.util.EnumMap;
/** Structure template processor that switches all wood in the structure to a certain given wood type. */
public class WoodTypeTemplateProcessor implements ITemplateProcessor {
private final BlockPlanks.EnumType woodType;
private final EnumMap<BlockPlanks.EnumType, Block> DOORS;
private final EnumMap<BlockPlanks.EnumType, Block> STAIRS;
private final EnumMap<BlockPlanks.EnumType, Block> FENCES;
private final EnumMap<BlockPlanks.EnumType, Block> FENCE_GATES;
/**
* Creates a new {@code WoodTypeTemplateProcessor} of the given type.
* @param woodType The wood type to be used.
*/
public WoodTypeTemplateProcessor(BlockPlanks.EnumType woodType){
this.woodType = woodType;
DOORS = new EnumMap<>(BlockPlanks.EnumType.class);
DOORS.put(BlockPlanks.EnumType.OAK, Blocks.OAK_DOOR);
DOORS.put(BlockPlanks.EnumType.SPRUCE, Blocks.SPRUCE_DOOR);
DOORS.put(BlockPlanks.EnumType.BIRCH, Blocks.BIRCH_DOOR);
DOORS.put(BlockPlanks.EnumType.JUNGLE, Blocks.JUNGLE_DOOR);
DOORS.put(BlockPlanks.EnumType.ACACIA, Blocks.ACACIA_DOOR);
DOORS.put(BlockPlanks.EnumType.DARK_OAK, Blocks.DARK_OAK_DOOR);
STAIRS = new EnumMap<>(BlockPlanks.EnumType.class);
STAIRS.put(BlockPlanks.EnumType.OAK, Blocks.OAK_STAIRS);
STAIRS.put(BlockPlanks.EnumType.SPRUCE, Blocks.SPRUCE_STAIRS);
STAIRS.put(BlockPlanks.EnumType.BIRCH, Blocks.BIRCH_STAIRS);
STAIRS.put(BlockPlanks.EnumType.JUNGLE, Blocks.JUNGLE_STAIRS);
STAIRS.put(BlockPlanks.EnumType.ACACIA, Blocks.ACACIA_STAIRS);
STAIRS.put(BlockPlanks.EnumType.DARK_OAK, Blocks.DARK_OAK_STAIRS);
FENCES = new EnumMap<>(BlockPlanks.EnumType.class);
FENCES.put(BlockPlanks.EnumType.OAK, Blocks.OAK_FENCE);
FENCES.put(BlockPlanks.EnumType.SPRUCE, Blocks.SPRUCE_FENCE);
FENCES.put(BlockPlanks.EnumType.BIRCH, Blocks.BIRCH_FENCE);
FENCES.put(BlockPlanks.EnumType.JUNGLE, Blocks.JUNGLE_FENCE);
FENCES.put(BlockPlanks.EnumType.ACACIA, Blocks.ACACIA_FENCE);
FENCES.put(BlockPlanks.EnumType.DARK_OAK, Blocks.DARK_OAK_FENCE);
FENCE_GATES = new EnumMap<>(BlockPlanks.EnumType.class);
FENCE_GATES.put(BlockPlanks.EnumType.OAK, Blocks.OAK_FENCE_GATE);
FENCE_GATES.put(BlockPlanks.EnumType.SPRUCE, Blocks.SPRUCE_FENCE_GATE);
FENCE_GATES.put(BlockPlanks.EnumType.BIRCH, Blocks.BIRCH_FENCE_GATE);
FENCE_GATES.put(BlockPlanks.EnumType.JUNGLE, Blocks.JUNGLE_FENCE_GATE);
FENCE_GATES.put(BlockPlanks.EnumType.ACACIA, Blocks.ACACIA_FENCE_GATE);
FENCE_GATES.put(BlockPlanks.EnumType.DARK_OAK, Blocks.DARK_OAK_FENCE_GATE);
}
@Nullable
@Override
public Template.BlockInfo processBlock(World world, BlockPos pos, Template.BlockInfo info){
// Why do these each have their own property key?
if(info.blockState.getBlock() instanceof BlockPlanks){
return new Template.BlockInfo(info.pos, info.blockState.withProperty(BlockPlanks.VARIANT, woodType), info.tileentityData);
}else if(info.blockState.getBlock() instanceof BlockWoodSlab){
return new Template.BlockInfo(info.pos, info.blockState.withProperty(BlockWoodSlab.VARIANT, woodType), info.tileentityData);
// This is a mess, no wonder the flattening happened
}else if(DOORS.containsValue(info.blockState.getBlock())){
return new Template.BlockInfo(info.pos, WizardryUtilities.copyState(DOORS.get(woodType), info.blockState), info.tileentityData);
}else if(STAIRS.containsValue(info.blockState.getBlock())){
return new Template.BlockInfo(info.pos, WizardryUtilities.copyState(STAIRS.get(woodType), info.blockState), info.tileentityData);
}else if(FENCES.containsValue(info.blockState.getBlock())){
return new Template.BlockInfo(info.pos, WizardryUtilities.copyState(FENCES.get(woodType), info.blockState), info.tileentityData);
}else if(FENCE_GATES.containsValue(info.blockState.getBlock())){
return new Template.BlockInfo(info.pos, WizardryUtilities.copyState(FENCE_GATES.get(woodType), info.blockState), info.tileentityData);
}
return info;
}
}
@@ -0,0 +1,61 @@
package electroblob.wizardry.worldgen;
import com.google.common.primitives.Ints;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.registry.WizardryBlocks;
import net.minecraft.block.state.IBlockState;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraft.world.chunk.IChunkProvider;
import net.minecraft.world.gen.IChunkGenerator;
import net.minecraftforge.fml.common.IWorldGenerator;
import java.util.Random;
public class WorldGenCrystalFlower implements IWorldGenerator {
@Override
public void generate(Random random, int chunkX, int chunkZ, World world, IChunkGenerator chunkGenerator, IChunkProvider chunkProvider){
if(Ints.contains(Wizardry.settings.flowerDimensions, world.provider.getDimension())){
this.generatePlant(WizardryBlocks.crystal_flower.getDefaultState(), world, random, 8 + chunkX * 16, 8 + chunkZ * 16, 2, 20);
}
}
/**
* Generates the specified plant randomly throughout the world.
*
* @param state The plant block
* @param world The world
* @param random A instance of {@code Random} to use
* @param x The x coordinate of the first block in the chunk
* @param z The y coordinate of the first block in the chunk
* @param chancesToSpawn Number of chances to spawn a flower patch
* @param groupSize The number of times to try generating a flower per flower patch spawn
*/
public void generatePlant(IBlockState state, World world, Random random, int x, int z, int chancesToSpawn, int groupSize){
for(int i = 0; i < chancesToSpawn; i++){
int randPosX = x + random.nextInt(16);
int randPosY = random.nextInt(256);
int randPosZ = z + random.nextInt(16);
for(int l = 0; l < groupSize; ++l){
int i1 = randPosX + random.nextInt(8) - random.nextInt(8);
int j1 = randPosY + random.nextInt(4) - random.nextInt(4);
int k1 = randPosZ + random.nextInt(8) - random.nextInt(8);
BlockPos pos = new BlockPos(i1, j1, k1);
if(world.isBlockLoaded(pos) && world.isAirBlock(pos) && (!world.provider.isNether() || j1 < 127)
&& state.getBlock().canPlaceBlockOnSide(world, pos, EnumFacing.UP)){
world.setBlockState(pos, state, 2);
}
}
}
}
}
@@ -0,0 +1,61 @@
package electroblob.wizardry.worldgen;
import com.google.common.primitives.Ints;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.registry.WizardryBlocks;
import net.minecraft.block.state.IBlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraft.world.chunk.IChunkProvider;
import net.minecraft.world.gen.IChunkGenerator;
import net.minecraft.world.gen.feature.WorldGenMinable;
import net.minecraftforge.fml.common.IWorldGenerator;
import java.util.Random;
public class WorldGenCrystalOre implements IWorldGenerator {
@Override
public void generate(Random random, int chunkX, int chunkZ, World world, IChunkGenerator chunkGenerator, IChunkProvider chunkProvider){
if(Ints.contains(Wizardry.settings.oreDimensions, world.provider.getDimension())){
this.addOreSpawn(WizardryBlocks.crystal_ore.getDefaultState(), world, random, chunkX * 16, chunkZ * 16, 16, 16, 5, 7, 5, 30);
}
}
/**
* Adds an Ore Spawn to Minecraft. Simply register all Ores to spawn with this method in your Generation method in
* your IWorldGeneration extending Class
*
* @param state The Block to spawn
* @param world The World to spawn in
* @param random A Random object for retrieving random positions within the world to spawn the Block
* @param blockXPos An int for passing the X-Coordinate for the Generation method
* @param blockZPos An int for passing the Z-Coordinate for the Generation method
* @param maxX An int for setting the maximum X-Coordinate values for spawning on the X-Axis on a Per-Chunk basis
* @param maxZ An int for setting the maximum Z-Coordinate values for spawning on the Z-Axis on a Per-Chunk basis
* @param maxVeinSize An int for setting the maximum size of a vein
* @param chancesToSpawn An int for the Number of chances available for the Block to spawn per-chunk
* @param minY An int for the minimum Y-Coordinate height at which this block may spawn
* @param maxY An int for the maximum Y-Coordinate height at which this block may spawn
**/
public void addOreSpawn(IBlockState state, World world, Random random, int blockXPos, int blockZPos, int maxX,
int maxZ, int maxVeinSize, int chancesToSpawn, int minY, int maxY){
// int maxPossY = minY + (maxY - 1);
assert maxY > minY : "The maximum Y must be greater than the Minimum Y";
assert maxX > 0 && maxX <= 16 : "addOreSpawn: The Maximum X must be greater than 0 and less than 16";
assert minY > 0 : "addOreSpawn: The Minimum Y must be greater than 0";
assert maxY < 256 && maxY > 0 : "addOreSpawn: The Maximum Y must be less than 256 but greater than 0";
assert maxZ > 0 && maxZ <= 16 : "addOreSpawn: The Maximum Z must be greater than 0 and less than 16";
int diffBtwnMinMaxY = maxY - minY;
for(int x = 0; x < chancesToSpawn; x++){
int posX = blockXPos + random.nextInt(maxX);
int posY = minY + random.nextInt(diffBtwnMinMaxY);
int posZ = blockZPos + random.nextInt(maxZ);
// N.B. This method applies the anti-cascading-lag offset itself
(new WorldGenMinable(state, maxVeinSize)).generate(world, random, new BlockPos(posX, posY, posZ));
}
}
}
@@ -0,0 +1,101 @@
package electroblob.wizardry.worldgen;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.block.BlockRunestone;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.integration.antiqueatlas.WizardryAntiqueAtlasIntegration;
import net.minecraft.init.Blocks;
import net.minecraft.tileentity.MobSpawnerBaseLogic;
import net.minecraft.tileentity.TileEntityMobSpawner;
import net.minecraft.util.Mirror;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraft.world.gen.structure.template.ITemplateProcessor;
import net.minecraft.world.gen.structure.template.PlacementSettings;
import net.minecraft.world.gen.structure.template.Template;
import org.apache.commons.lang3.ArrayUtils;
import java.util.EnumMap;
import java.util.Map;
import java.util.Random;
public class WorldGenObelisk extends WorldGenSurfaceStructure {
private static final String SPAWNER_DATA_BLOCK_TAG = "spawner";
private static final EnumMap<Element, ResourceLocation> MOB_TYPES = new EnumMap<>(Element.class);
static {
MOB_TYPES.put(Element.FIRE, new ResourceLocation(Wizardry.MODID, "blaze_minion"));
MOB_TYPES.put(Element.ICE, new ResourceLocation(Wizardry.MODID, "ice_wraith"));
MOB_TYPES.put(Element.LIGHTNING, new ResourceLocation(Wizardry.MODID, "lightning_wraith"));
MOB_TYPES.put(Element.NECROMANCY, new ResourceLocation(Wizardry.MODID, "wither_skeleton_minion"));
MOB_TYPES.put(Element.EARTH, new ResourceLocation(Wizardry.MODID, "spider_minion"));
MOB_TYPES.put(Element.SORCERY, new ResourceLocation(Wizardry.MODID, "vex_minion"));
MOB_TYPES.put(Element.HEALING, new ResourceLocation(Wizardry.MODID, "husk_minion"));
}
@Override
public String getStructureName(){
return "obelisk";
}
@Override
public long getRandomSeedModifier(){
return 19348242L;
}
@Override
public Mirror[] getValidMirrors(){
return new Mirror[]{Mirror.NONE}; // It's symmetrical so there's no point mirroring it
}
@Override
public boolean canGenerate(Random random, World world, int chunkX, int chunkZ){
return ArrayUtils.contains(Wizardry.settings.obeliskDimensions, world.provider.getDimension())
&& Wizardry.settings.obeliskRarity > 0 && random.nextInt(Wizardry.settings.obeliskRarity) == 0;
}
@Override
public ResourceLocation getStructureFile(Random random){
return Wizardry.settings.obeliskFiles[random.nextInt(Wizardry.settings.obeliskFiles.length)];
}
@Override
public void spawnStructure(Random random, World world, BlockPos origin, Template template, PlacementSettings settings, ResourceLocation structureFile){
final Element element = Element.values()[1 + random.nextInt(Element.values().length-1)];
ITemplateProcessor processor = (w, p, i) -> i.blockState.getBlock() instanceof BlockRunestone ? new Template.BlockInfo(
i.pos, i.blockState.withProperty(BlockRunestone.ELEMENT, element), i.tileentityData) : i;
template.addBlocksToWorld(world, origin, processor, settings, 2);
WizardryAntiqueAtlasIntegration.markObelisk(world, origin.getX(), origin.getZ());
// Mob spawner
Map<BlockPos, String> dataBlocks = template.getDataBlocks(origin, settings);
for(Map.Entry<BlockPos, String> entry : dataBlocks.entrySet()){
if(entry.getValue().equals(SPAWNER_DATA_BLOCK_TAG)){
world.setBlockState(entry.getKey(), Blocks.MOB_SPAWNER.getDefaultState());
if(world.getTileEntity(entry.getKey()) instanceof TileEntityMobSpawner){
MobSpawnerBaseLogic spawnerLogic = ((TileEntityMobSpawner)world.getTileEntity(entry.getKey())).getSpawnerBaseLogic();
spawnerLogic.setEntityId(MOB_TYPES.get(element));
}else{
Wizardry.logger.info("Tried to set the mob spawned by an obelisk, but the expected TileEntityMobSpawner was not present");
}
}else{
// This probably shouldn't happen...
Wizardry.logger.info("Unrecognised data block value {} in structure {}", entry.getValue(), structureFile);
}
}
}
}
@@ -0,0 +1,95 @@
package electroblob.wizardry.worldgen;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.block.BlockPedestal;
import electroblob.wizardry.block.BlockRunestone;
import electroblob.wizardry.constants.Element;
import electroblob.wizardry.integration.antiqueatlas.WizardryAntiqueAtlasIntegration;
import electroblob.wizardry.registry.WizardryBlocks;
import electroblob.wizardry.spell.ArcaneLock;
import electroblob.wizardry.tileentity.TileEntityShrineCore;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import net.minecraft.world.gen.structure.template.ITemplateProcessor;
import net.minecraft.world.gen.structure.template.PlacementSettings;
import net.minecraft.world.gen.structure.template.Template;
import org.apache.commons.lang3.ArrayUtils;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
public class WorldGenShrine extends WorldGenSurfaceStructure {
private static final String CORE_DATA_BLOCK_TAG = "core";
@Override
public String getStructureName(){
return "shrine";
}
@Override
public long getRandomSeedModifier(){
return 17502749L;
}
@Override
public boolean canGenerate(Random random, World world, int chunkX, int chunkZ){
return ArrayUtils.contains(Wizardry.settings.shrineDimensions, world.provider.getDimension())
&& Wizardry.settings.shrineRarity > 0 && random.nextInt(Wizardry.settings.shrineRarity) == 0;
}
@Override
public ResourceLocation getStructureFile(Random random){
return Wizardry.settings.shrineFiles[random.nextInt(Wizardry.settings.shrineFiles.length)];
}
@Override
public void spawnStructure(Random random, World world, BlockPos origin, Template template, PlacementSettings settings, ResourceLocation structureFile){
final Element element = Element.values()[1 + random.nextInt(Element.values().length-1)];
ITemplateProcessor processor = (w, p, i) -> i.blockState.getBlock() instanceof BlockRunestone ? new Template.BlockInfo(
i.pos, i.blockState.withProperty(BlockRunestone.ELEMENT, element), i.tileentityData) : i;
template.addBlocksToWorld(world, origin, processor, settings, 2);
WizardryAntiqueAtlasIntegration.markShrine(world, origin.getX(), origin.getZ());
// Shrine core
Map<BlockPos, String> dataBlocks = template.getDataBlocks(origin, settings);
for(Map.Entry<BlockPos, String> entry : dataBlocks.entrySet()){
if(entry.getValue().equals(CORE_DATA_BLOCK_TAG)){
// This bit could have been done with a template processor, but we also need to link the chest and lock it
world.setBlockState(entry.getKey(), WizardryBlocks.runestone_pedestal.getDefaultState()
.withProperty(BlockPedestal.ELEMENT, element).withProperty(BlockPedestal.NATURAL, true));
TileEntity core = world.getTileEntity(entry.getKey());
TileEntity container = world.getTileEntity(entry.getKey().up());
if(container != null){
container.getTileData().setUniqueId(ArcaneLock.NBT_KEY, new UUID(0, 0)); // Nil UUID
if(core instanceof TileEntityShrineCore){
((TileEntityShrineCore)core).linkContainer(container);
}else{
Wizardry.logger.info("What?!");
}
}else{
Wizardry.logger.info("Expected chest or other container at {} in structure {}, found no tile entity", entry.getKey(), structureFile);
}
}else{
// This probably shouldn't happen...
Wizardry.logger.info("Unrecognised data block value {} in structure {}", entry.getValue(), structureFile);
}
}
}
}
@@ -0,0 +1,450 @@
package electroblob.wizardry.worldgen;
import com.google.common.math.Quantiles;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.registry.WizardryAdvancementTriggers;
import electroblob.wizardry.util.WizardryUtilities;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import net.minecraft.block.Block;
import net.minecraft.block.BlockLeaves;
import net.minecraft.block.BlockLog;
import net.minecraft.entity.Entity;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.init.Blocks;
import net.minecraft.nbt.NBTBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.Mirror;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.Rotation;
import net.minecraft.util.math.*;
import net.minecraft.world.World;
import net.minecraft.world.chunk.IChunkProvider;
import net.minecraft.world.gen.IChunkGenerator;
import net.minecraft.world.gen.structure.MapGenStructureData;
import net.minecraft.world.gen.structure.StructureBoundingBox;
import net.minecraft.world.gen.structure.template.PlacementSettings;
import net.minecraft.world.gen.structure.template.Template;
import net.minecraftforge.common.BiomeDictionary;
import net.minecraftforge.common.util.Constants;
import net.minecraftforge.fml.common.IWorldGenerator;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import javax.annotation.Nullable;
import java.util.*;
/** Base structure generation class which handles code common to all wizardry's above-ground structures, such as
* calculating the median ground level. */
@Mod.EventBusSubscriber
public abstract class WorldGenSurfaceStructure implements IWorldGenerator {
/** The maximum fraction of the area where a structure is to be spawned that may be covered by liquid. */
private static final float MAX_LIQUID_FRACTION = 0.4f;
/** Static map used to store all structure generators for the purpose of advancements. */
private static final Map<String, WorldGenSurfaceStructure> generators = new HashMap<>();
/** A random instance used solely for the purpose of emulating the world generation to predict locations. */
private final Random random;
private World world;
private MapGenStructureData structureData;
/** Stores the bounding boxes of all structures of this type that have been generated so far. */
protected final Long2ObjectMap<StructureBoundingBox> structureMap = new Long2ObjectOpenHashMap<>(1024);
public WorldGenSurfaceStructure(){
random = new Random(); // Seed will be set later
generators.put(this.getStructureName(), this);
}
/** Returns a constant (but unique) long value used to change the random seed so that each generator produces a
* different sequence of numbers. Without this, all wizardry's generators attempt to generate in the same chunks
* when set to the same rarity. */
public abstract long getRandomSeedModifier();
/** Pre-check for whether the structure can generate. Usually this is just used for randomisation so that
* calculations are only performed for chunks that will generate a structure; most placement-specific stuff
* can just be done using a check inside {@link WorldGenSurfaceStructure#spawnStructure(Random, World, BlockPos, Template, PlacementSettings, ResourceLocation)} */
public abstract boolean canGenerate(Random random, World world, int chunkX, int chunkZ);
/** Called each time the structure is generated to get a structure file to use. */
public abstract ResourceLocation getStructureFile(Random random);
/** Returns the name of this structure type, which is used as an identifier in the world save file and for
* advancement JSON files. */
public abstract String getStructureName();
/**
* Spawns the structure at the given origin with the given placement settings.
* @param random A {@code Random} instance to use for any further parameters that need randomising.
* @param world The world to spawn the structure in.
* @param origin The origin coordinates of the structure in the world, pre-adjusted for floor height and rotation
* to avoid floating structures and minimise cascading worldgen lag.
* @param template The template to be generated.
* @param settings The placement settings for the structure.
* @param structureFile The location of the chosen structure file, for logging purposes.
*/
public abstract void spawnStructure(Random random, World world, BlockPos origin, Template template, PlacementSettings settings, ResourceLocation structureFile);
/** Specifies valid rotation values for the structure. By default this returns all rotations. */
public Rotation[] getValidRotations(){
return Rotation.values();
}
/** Specifies valid rotation values for the structure. By default this returns an array of {@code Mirror.LEFT_RIGHT}
* and {@code Mirror.NONE}. */
public Mirror[] getValidMirrors(){
return new Mirror[]{Mirror.NONE, Mirror.LEFT_RIGHT};
}
/**
* Finds a random position within the given chunk at which the given template may be generated.
* In an effort to make structure rarity more uniform, they now get a number of tries to spawn in each
* randomly-selected chunk so they have a better chance of avoiding stuff that might be in the way (cliffs,
* villages, lakes, etc.).
* <p></p>
* This method calculates the median floor height to ensure that sudden changes in level are ignored and the
* structure is always spawned at the same level as the majority of the underlying floor. Trees are also ignored
* when determining floor level, so that forests don't impede structure spawning.
*
* @param template The template to be generated
* @param settings The placement settings for the structure template
* @param random A random instance to use. This should have had its seed set according to the world seed and chunk
* coordinates.
* @param world The world in which to spawn the structure
* @param chunkX The x-coordinate of the chunk being populated
* @param chunkZ The z-coordinate of the chunk being populated
* @return The coordinates of the position found, or null if no suitable position was found. The returned
* {@code BlockPos} is <b>always</b> the northwest corner of the structure, and the y-coordinate is that of the
* uppermost block at those (x, z) coordinates. If the structure is being rotated this needs to be altered using
* {@link Template#getZeroPositionWithTransform(BlockPos, Mirror, Rotation)} before it can be fed into the template
* spawning methods.
*/
@Nullable
protected BlockPos findValidPosition(Template template, PlacementSettings settings, Random random, World world,
int chunkX, int chunkZ){
// Offset by (8, 8) to minimise cascading worldgen lag
// See https://www.reddit.com/r/feedthebeast/cowmments/5x0twz/investigating_extreme_worldgen_lag/?ref=share&ref_source=embed&utm_content=title&utm_medium=post_embed&utm_name=c07cbb545f74487793783012794733d8&utm_source=embedly&utm_term=5x0twz
// Multiplying and left-shifting are identical but it's good practice to bitshift here I guess
BlockPos origin = new BlockPos(8 + (chunkX << 4) + random.nextInt(16), 0, 8 + (chunkZ << 4) + random.nextInt(16));
BlockPos size = template.transformedSize(settings.getRotation());
// Estimate a starting height for searching for the floor
BlockPos centre = world.getTopSolidOrLiquidBlock(new BlockPos(origin.add(size.getX()/2, 0, size.getZ()/2)));
Integer startingHeight = WizardryUtilities.getNearestSurface(world, centre, EnumFacing.UP, 32, true,
WizardryUtilities.SurfaceCriteria.COLLIDABLE_IGNORING_TREES);
if(startingHeight == null) return null;
if(Wizardry.settings.fastWorldgen){
BlockPos result = origin.up(startingHeight);
// Fast worldgen doesn't check for water, instead it checks the biome like vanilla, which is crude but fast
return BiomeDictionary.hasType(world.getBiome(result), BiomeDictionary.Type.WATER) ? null : result;
}
int[] floorHeights = new int[size.getX() * size.getZ()];
int liquidCount = 0;
for(int i = 0; i < floorHeights.length; i++){
// Despite what its name suggests, this method does not return the position of a liquid. It is in fact
// exactly what is needed here since it is used for placing villages and stuff, and doesn't include leaves
// or other foliage.
BlockPos pos = origin.add(i / size.getZ(), 0, i % size.getZ());
Integer floor = WizardryUtilities.getNearestSurface(world, pos.up(startingHeight), EnumFacing.UP, 32, true,
WizardryUtilities.SurfaceCriteria.COLLIDABLE_IGNORING_TREES);
floorHeights[i] = floor == null ? 0 : floor; // Very unlikely that floor is null
// ^ That method gets the top solid block. Most non-solid blocks are ok to have around the structure,
// with the exception of liquids, so if there are too many the position is deemed unsuitable.
if(world.getBlockState(pos.up(floorHeights[i])).getMaterial().isLiquid()) liquidCount++;
if(liquidCount > floorHeights.length * MAX_LIQUID_FRACTION) return null;
}
// Get the median floor height (rather than the mean, that way cliffs should have no effect)
int medianFloorHeight = MathHelper.floor(Quantiles.median().compute(floorHeights));
// Now we know the y level of the base of the structure, we can check for stuff in the way
// A structure is deemed to have stuff in the way if the floor level at any of the (x, z) positions it
// occupies differs from the base y level by more than its distance from the centre plus a constant.
// In practical terms, this means structures can't spawn on steep slopes or inside cave mouths or buildings.
for(int i = 0; i < floorHeights.length; i++){
int orthogonalDist = Math.max(Math.abs(i / size.getZ() - size.getX()/2), Math.abs(i % size.getZ() - size.getZ()/2));
if(Math.abs(floorHeights[i] - medianFloorHeight) > Math.max(2, orthogonalDist)) return null; // Something is in the way
}
return origin.up(medianFloorHeight - 1);
}
@Override
public void generate(Random random, int chunkX, int chunkZ, World world, IChunkGenerator chunkGenerator, IChunkProvider chunkProvider){
if(!world.getWorldInfo().isMapFeaturesEnabled()) return;
// Don't need to worry about overflows because they'll just wrap around, which is fine for this purpose
random.setSeed(random.nextLong() + getRandomSeedModifier());
initializeStructureData(world); // Load the data from the save file if it isn't already loaded
if(canGenerate(random, world, chunkX, chunkZ)){
ResourceLocation structureFile = getStructureFile(random);
Template template = world.getSaveHandler().getStructureTemplateManager().getTemplate(
world.getMinecraftServer(), structureFile);
Rotation[] rotations = getValidRotations();
Mirror[] mirrors = getValidMirrors();
PlacementSettings settings = new PlacementSettings()
.setRotation(rotations[random.nextInt(rotations.length)])
.setMirror(mirrors[random.nextInt(mirrors.length)]);
int triesLeft = 10;
BlockPos origin;
do {
origin = findValidPosition(template, settings, random, world, chunkX, chunkZ);
triesLeft--;
}while(triesLeft > 0 && origin != null);
if(origin == null) return;
// Need to subtract 1 from each coordinate since both corners are inclusive
StructureBoundingBox box = new StructureBoundingBox(origin, origin.add(template.transformedSize(settings.getRotation())).add(-1, -1, -1));
// DEBUG
// world.setBlockState(new BlockPos(box.minX, box.minY, box.minZ), Blocks.CONCRETE.getDefaultState().withProperty(BlockColored.COLOR, EnumDyeColor.MAGENTA));
// world.setBlockState(new BlockPos(box.maxX, box.maxY, box.maxZ), Blocks.CONCRETE.getDefaultState().withProperty(BlockColored.COLOR, EnumDyeColor.MAGENTA));
if(!Wizardry.settings.fastWorldgen){
for(WorldGenSurfaceStructure generator : generators.values()){
StructureBoundingBox otherbox = generator.structureMap.get(ChunkPos.asLong(origin.getX() >> 4, origin.getZ() >> 4));
if(otherbox != null && otherbox.intersectsWith(box)) return;
}
}
settings.setBoundingBox(box);
// PlacementSettings rotates and mirrors the structure around the origin, keeping the origin in the same
// place in the world. This means the structure can be rotated/mirrored into the 8 block border, undoing all
// our hard work to try and prevent cascading worldgen lag!
// To properly minimise cascading worldgen lag, the method below returns the position where the corner needs
// to be such that the original structure's NW (-X, -Z) corner is at the origin.
origin = template.getZeroPositionWithTransform(origin, settings.getMirror(), settings.getRotation());
spawnStructure(random, world, origin, template, settings, structureFile);
if(!Wizardry.settings.fastWorldgen) removeFloatingTrees(world, box);
structureMap.put(ChunkPos.asLong(origin.getX() >> 4, origin.getZ() >> 4), settings.getBoundingBox());
NBTTagCompound tag = new NBTTagCompound();
tag.setInteger("ChunkX", chunkX);
tag.setInteger("ChunkZ", chunkZ);
tag.setTag("BB", settings.getBoundingBox().toNBTTagIntArray());
structureData.writeInstance(tag, chunkX, chunkZ);
structureData.markDirty();
}
}
/** Copied from MapGenStructure. Unlike most NBT loading, this is lazy - it only gets read from NBT when requested. */
protected void initializeStructureData(World world){
// If the world that was last generated is not this world, load the data for the new world
// This is a bit of a dirty hack, it would be better if we had separate instances per-world but... effort...
// For now it works, maybe one day I'll improve it
if(world != this.world){
this.world = world;
this.structureData = (MapGenStructureData)world.getPerWorldStorage().getOrLoadData(MapGenStructureData.class, this.getStructureName());
// This has to be cleared or worlds will interfere with each other!
// Vanilla doesn't have to do this because each world has a separate ChunkGenerator which stores MapGenBase
// instances
this.structureMap.clear();
if(this.structureData == null){
this.structureData = new MapGenStructureData(this.getStructureName());
world.getPerWorldStorage().setData(this.getStructureName(), this.structureData);
}else{
NBTTagCompound nbt = this.structureData.getTagCompound();
for(String s : nbt.getKeySet()){
NBTBase nbtbase = nbt.getTag(s);
if(nbtbase.getId() == Constants.NBT.TAG_COMPOUND){
NBTTagCompound entry = (NBTTagCompound)nbtbase;
if(entry.hasKey("ChunkX") && entry.hasKey("ChunkZ") && entry.hasKey("BB")){
int i = entry.getInteger("ChunkX");
int j = entry.getInteger("ChunkZ");
int[] coords = entry.getIntArray("BB");
this.structureMap.put(ChunkPos.asLong(i, j), new StructureBoundingBox(coords));
}
}
}
}
}
}
/** Finds and removes any floating bits of tree in and above the given structure bounding box. */
protected static void removeFloatingTrees(World world, StructureBoundingBox boundingBox){
boolean changed = true;
int y = boundingBox.minY;
// Remove all the logs
while(changed && y < world.getHeight()){ // I do hope the trees don't reach the world height...
// Always checks at least the first layer above the bounding box in case the structure cut the rest off
if(y > boundingBox.maxY + 1) changed = false;
for(int x = boundingBox.minX; x <= boundingBox.maxX; x++){
for(int z = boundingBox.minZ; z <= boundingBox.maxZ; z++){
BlockPos pos = new BlockPos(x, y, z);
Block block = world.getBlockState(pos).getBlock();
Block below = world.getBlockState(pos.down()).getBlock();
if(block instanceof BlockLog){
if(below != Blocks.GRASS && below != Blocks.DIRT && !(below instanceof BlockLog) &&
!below.isLeaves(world.getBlockState(pos.down()), world, pos.down())){
world.setBlockToAir(pos);
changed = true;
}
}
}
}
y++;
}
// Now update all leaves in the area 16 times to make them decay
int border = 8;
List<BlockPos> leaves = new ArrayList<>();
for(int x = boundingBox.minX - border; x <= boundingBox.maxX + border; x++){
for(int y1 = boundingBox.minY - border; y1 <= y + border; y1++){
for(int z = boundingBox.minZ - border; z <= boundingBox.maxZ + border; z++){
BlockPos pos = new BlockPos(x, y1, z);
if(world.getBlockState(pos).getBlock() instanceof BlockLeaves) leaves.add(pos);
}
}
}
for(int i=0; i<16; i++){
leaves.forEach(p -> world.getBlockState(p).getBlock().updateTick(world, p, world.getBlockState(p), null));
}
// Finally, remove all the items that were dropped as a result of leaf decay
AxisAlignedBB box = new AxisAlignedBB(boundingBox.minX, boundingBox.minY, boundingBox.minZ, boundingBox.maxX, y, boundingBox.maxZ).grow(border);
world.getEntitiesWithinAABB(EntityItem.class, box).forEach(Entity::setDead);
}
/** Returns true if the given position is within a structure of this type in the given world, false
* otherwise. This will not work on chunks that are yet to be generated; attempting to do so will print a
* warning to the console. */
public boolean isInsideStructure(World world, double x, double y, double z){
initializeStructureData(world); // Load the data from the save file if it isn't already loaded
int chunkX = (int)x >> 4;
int chunkZ = (int)z >> 4;
if(!world.isChunkGeneratedAt(chunkX, chunkZ)){
Wizardry.logger.warn("Testing whether position ({}, {}, {}) is inside a structure, but that chunk hasn't been generated yet", x, y, z);
return false;
}
// Vanilla just iterates through the entire structure map, but we can be a little more intelligent
// about it by only testing the chunks near the player (since all the structures are smaller than 32x32)
long[] chunks = {ChunkPos.asLong(chunkX - 1, chunkZ - 1), ChunkPos.asLong(chunkX - 1, chunkZ), ChunkPos.asLong(chunkX - 1, chunkZ + 1),
ChunkPos.asLong(chunkX, chunkZ - 1), ChunkPos.asLong(chunkX, chunkZ), ChunkPos.asLong(chunkX, chunkZ + 1),
ChunkPos.asLong(chunkX + 1, chunkZ - 1), ChunkPos.asLong(chunkX + 1, chunkZ), ChunkPos.asLong(chunkX + 1, chunkZ + 1)};
for(long chunkPos : chunks){
if(structureMap.containsKey(chunkPos) && structureMap.get(chunkPos).isVecInside(new Vec3i(x, y, z)))
return true;
}
return false;
}
/** Copied from MapGenMineshaft. The general idea (it seems) is to emulate the world generator's randomisation
* without actually placing any blocks. Presumably mineshafts don't need sub-chunk randomisation? */
public BlockPos getNearestStructurePos(World world, BlockPos pos, boolean findUnexplored){
// TODO: We have a problem here, in that the 'pragmatic' placement algorithm (good as it is) requires
// the chunk to have already been generated, so we can't be sure if a structure actually exists until
// the chunk is actually generated.
int j = pos.getX() >> 4;
int k = pos.getZ() >> 4;
for (int l = 0; l <= 1000; ++l)
{
for (int i1 = -l; i1 <= l; ++i1)
{
boolean flag = i1 == -l || i1 == l;
for (int j1 = -l; j1 <= l; ++j1)
{
boolean flag1 = j1 == -l || j1 == l;
if (flag || flag1)
{
// TESTME: Is this the same as Forge's per-chunk seeds? (see caller of generate())
int k1 = j + i1;
int l1 = k + j1;
this.random.setSeed((long)(k1 ^ l1) ^ world.getSeed());
this.random.nextInt();
if(this.canGenerate(this.random, world, k1, l1) && (!findUnexplored || !world.isChunkGeneratedAt(k1, l1))){
return new BlockPos((k1 << 4) + 8, 64, (l1 << 4) + 8);
}
}
}
}
}
return null;
}
/** Returns the world generator with the given name. */
public static WorldGenSurfaceStructure byName(String name){
return generators.get(name);
}
@SubscribeEvent
public static void onPlayerTick(TickEvent.PlayerTickEvent event){
if(event.player instanceof EntityPlayerMP && event.player.ticksExisted % 20 == 0){
WizardryAdvancementTriggers.visit_structure.trigger((EntityPlayerMP)event.player);
}
}
}
@@ -0,0 +1,165 @@
package electroblob.wizardry.worldgen;
import com.google.common.collect.ImmutableMap;
import electroblob.wizardry.Wizardry;
import electroblob.wizardry.entity.living.EntityEvilWizard;
import electroblob.wizardry.entity.living.EntityWizard;
import electroblob.wizardry.integration.antiqueatlas.WizardryAntiqueAtlasIntegration;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.block.BlockPlanks;
import net.minecraft.block.BlockStainedHardenedClay;
import net.minecraft.block.state.IBlockState;
import net.minecraft.init.Biomes;
import net.minecraft.init.Blocks;
import net.minecraft.item.EnumDyeColor;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.gen.structure.template.ITemplateProcessor;
import net.minecraft.world.gen.structure.template.PlacementSettings;
import net.minecraft.world.gen.structure.template.Template;
import net.minecraftforge.common.BiomeDictionary;
import org.apache.commons.lang3.ArrayUtils;
import java.util.HashSet;
import java.util.Map;
import java.util.Random;
import java.util.Set;
public class WorldGenWizardTower extends WorldGenSurfaceStructure {
// TODO: Add wizard towers to the /locate command
// This requires some careful manipulation of Random objects to replicate the positions exactly for the current
// world. See the end of ChunkGeneratorOverworld for the relevant methods.
private static final String WIZARD_DATA_BLOCK_TAG = "wizard";
private static final String EVIL_WIZARD_DATA_BLOCK_TAG = "evil_wizard";
private final Map<BiomeDictionary.Type, IBlockState> SPECIAL_WALL_BLOCKS;
public WorldGenWizardTower(){
// These are initialised here because it's a convenient point after the blocks are registered
SPECIAL_WALL_BLOCKS = ImmutableMap.of(
BiomeDictionary.Type.MESA, Blocks.RED_SANDSTONE.getDefaultState(),
BiomeDictionary.Type.MOUNTAIN, Blocks.STONEBRICK.getDefaultState(),
BiomeDictionary.Type.NETHER, Blocks.NETHER_BRICK.getDefaultState(),
BiomeDictionary.Type.SANDY, Blocks.SANDSTONE.getDefaultState()
);
}
@Override
public String getStructureName(){
return "wizard_tower";
}
@Override
public long getRandomSeedModifier(){
return 10473957L; // Yep, I literally typed 8 digits at random
}
@Override
public boolean canGenerate(Random random, World world, int chunkX, int chunkZ){
return ArrayUtils.contains(Wizardry.settings.towerDimensions, world.provider.getDimension())
&& Wizardry.settings.towerRarity > 0 && random.nextInt(Wizardry.settings.towerRarity) == 0;
}
@Override
public ResourceLocation getStructureFile(Random random){
return random.nextDouble() < Wizardry.settings.evilWizardChance ?
Wizardry.settings.towerWithChestFiles[random.nextInt(Wizardry.settings.towerWithChestFiles.length)] :
Wizardry.settings.towerFiles[random.nextInt(Wizardry.settings.towerFiles.length)];
}
@Override
public void spawnStructure(Random random, World world, BlockPos origin, Template template, PlacementSettings settings, ResourceLocation structureFile){
final EnumDyeColor colour = EnumDyeColor.values()[random.nextInt(EnumDyeColor.values().length)];
final Biome biome = world.getBiome(origin);
final IBlockState wallMaterial = SPECIAL_WALL_BLOCKS.keySet().stream().filter(t -> BiomeDictionary.hasType(biome, t))
.findFirst().map(SPECIAL_WALL_BLOCKS::get).orElse(Blocks.COBBLESTONE.getDefaultState());
final float mossiness = getBiomeMossiness(biome);
final BlockPlanks.EnumType woodType = getBiomeWoodVariant(biome);
final Set<BlockPos> blocksPlaced = new HashSet<>();
ITemplateProcessor processor = new MultiTemplateProcessor(true,
// Roof colour
(w, p, i) -> i.blockState.getBlock() instanceof BlockStainedHardenedClay ? new Template.BlockInfo(
i.pos, i.blockState.withProperty(BlockStainedHardenedClay.COLOR, colour), i.tileentityData) : i,
// Wall material
(w, p, i) -> i.blockState.getBlock() == Blocks.COBBLESTONE ? new Template.BlockInfo(i.pos,
wallMaterial, i.tileentityData) : i,
// Wood type
new WoodTypeTemplateProcessor(woodType),
// Mossifier
new MossifierTemplateProcessor(mossiness, 0.04f, origin.getY() + 1),
// Block recording (the process() method doesn't get called for structure voids)
(w, p, i) -> {if(i.blockState.getBlock() != Blocks.AIR) blocksPlaced.add(p); return i;}
);
template.addBlocksToWorld(world, origin, processor, settings, 2);
WizardryAntiqueAtlasIntegration.markTower(world, origin.getX(), origin.getZ());
// Wizard spawning
Map<BlockPos, String> dataBlocks = template.getDataBlocks(origin, settings);
for(Map.Entry<BlockPos, String> entry : dataBlocks.entrySet()){
Vec3d vec = WizardryUtilities.getCentre(entry.getKey());
if(entry.getValue().equals(WIZARD_DATA_BLOCK_TAG)){
EntityWizard wizard = new EntityWizard(world);
wizard.setLocationAndAngles(vec.x, vec.y, vec.z, 0, 0);
wizard.onInitialSpawn(world.getDifficultyForLocation(origin), null);
wizard.setTowerBlocks(blocksPlaced);
world.spawnEntity(wizard);
}else if(entry.getValue().equals(EVIL_WIZARD_DATA_BLOCK_TAG)){
EntityEvilWizard wizard = new EntityEvilWizard(world);
wizard.setLocationAndAngles(vec.x, vec.y, vec.z, 0, 0);
wizard.hasStructure = true; // Stops it despawning
wizard.onInitialSpawn(world.getDifficultyForLocation(origin), null);
world.spawnEntity(wizard);
}else{
// This probably shouldn't happen...
Wizardry.logger.info("Unrecognised data block value {} in structure {}", entry.getValue(), structureFile);
}
}
}
private static float getBiomeMossiness(Biome biome){
if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.DENSE)) return 0.7f;
if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.JUNGLE)) return 0.7f;
if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.WET)) return 0.5f;
if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.SWAMP)) return 0.5f;
if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.FOREST)) return 0.3f;
if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.LUSH)) return 0.3f;
if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.DRY)) return 0;
if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.COLD)) return 0;
if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.DEAD)) return 0;
if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.WASTELAND)) return 0;
if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.NETHER)) return 0;
return 0.1f; // Everything else (plains, etc.) has a small amount of moss
}
private static BlockPlanks.EnumType getBiomeWoodVariant(Biome biome){
// Unfortunately, I can't check all the wood types with the biome dictionary
if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.CONIFEROUS)) return BlockPlanks.EnumType.SPRUCE;
if(biome == Biomes.BIRCH_FOREST || biome == Biomes.BIRCH_FOREST_HILLS) return BlockPlanks.EnumType.BIRCH;
if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.JUNGLE)) return BlockPlanks.EnumType.JUNGLE;
if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.SAVANNA)) return BlockPlanks.EnumType.ACACIA;
// Not technically a tree type, but I think it fits quite well anyway
if(BiomeDictionary.hasType(biome, BiomeDictionary.Type.SPOOKY)) return BlockPlanks.EnumType.DARK_OAK;
// Everything else is oak
return BlockPlanks.EnumType.OAK;
}
}