Add library ruins
- Adds two variants of library ruins: surface (generates in forested biomes) and underground (generates everywhere) - Adds loot table support to bookshelves - Adds antique atlas markers for library ruins - Adds bookshelves and lecterns to the wood type template processor - Makes bookshelves and lecterns rotate correctly in structure templates - Generalises most of the methods in WorldGenSurfaceStructure to a new superclass WorldGenWizardryStructure
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package electroblob.wizardry.worldgen;
|
||||
|
||||
import electroblob.wizardry.registry.WizardryBlocks;
|
||||
import electroblob.wizardry.util.BlockUtils;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockPlanks;
|
||||
@@ -18,10 +19,13 @@ public class WoodTypeTemplateProcessor implements ITemplateProcessor {
|
||||
|
||||
private final BlockPlanks.EnumType woodType;
|
||||
|
||||
// Not all of these are used in wizardry's structures, but they're included for completeness
|
||||
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;
|
||||
private final EnumMap<BlockPlanks.EnumType, Block> BOOKSHELVES;
|
||||
private final EnumMap<BlockPlanks.EnumType, Block> LECTERNS;
|
||||
|
||||
/**
|
||||
* Creates a new {@code WoodTypeTemplateProcessor} of the given type.
|
||||
@@ -32,36 +36,52 @@ public class WoodTypeTemplateProcessor implements ITemplateProcessor {
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
|
||||
BOOKSHELVES = new EnumMap<>(BlockPlanks.EnumType.class);
|
||||
BOOKSHELVES.put(BlockPlanks.EnumType.OAK, WizardryBlocks.oak_bookshelf);
|
||||
BOOKSHELVES.put(BlockPlanks.EnumType.SPRUCE, WizardryBlocks.spruce_bookshelf);
|
||||
BOOKSHELVES.put(BlockPlanks.EnumType.BIRCH, WizardryBlocks.birch_bookshelf);
|
||||
BOOKSHELVES.put(BlockPlanks.EnumType.JUNGLE, WizardryBlocks.jungle_bookshelf);
|
||||
BOOKSHELVES.put(BlockPlanks.EnumType.ACACIA, WizardryBlocks.acacia_bookshelf);
|
||||
BOOKSHELVES.put(BlockPlanks.EnumType.DARK_OAK, WizardryBlocks.dark_oak_bookshelf);
|
||||
|
||||
LECTERNS = new EnumMap<>(BlockPlanks.EnumType.class);
|
||||
LECTERNS.put(BlockPlanks.EnumType.OAK, WizardryBlocks.oak_lectern);
|
||||
LECTERNS.put(BlockPlanks.EnumType.SPRUCE, WizardryBlocks.spruce_lectern);
|
||||
LECTERNS.put(BlockPlanks.EnumType.BIRCH, WizardryBlocks.birch_lectern);
|
||||
LECTERNS.put(BlockPlanks.EnumType.JUNGLE, WizardryBlocks.jungle_lectern);
|
||||
LECTERNS.put(BlockPlanks.EnumType.ACACIA, WizardryBlocks.acacia_lectern);
|
||||
LECTERNS.put(BlockPlanks.EnumType.DARK_OAK, WizardryBlocks.dark_oak_lectern);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -69,7 +89,7 @@ public class WoodTypeTemplateProcessor implements ITemplateProcessor {
|
||||
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){
|
||||
if(info.blockState.getBlock() instanceof BlockPlanks){ // This covers gilded wood too
|
||||
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);
|
||||
@@ -82,6 +102,10 @@ public class WoodTypeTemplateProcessor implements ITemplateProcessor {
|
||||
return new Template.BlockInfo(info.pos, BlockUtils.copyState(FENCES.get(woodType), info.blockState), info.tileentityData);
|
||||
}else if(FENCE_GATES.containsValue(info.blockState.getBlock())){
|
||||
return new Template.BlockInfo(info.pos, BlockUtils.copyState(FENCE_GATES.get(woodType), info.blockState), info.tileentityData);
|
||||
}else if(BOOKSHELVES.containsValue(info.blockState.getBlock())){
|
||||
return new Template.BlockInfo(info.pos, BlockUtils.copyState(BOOKSHELVES.get(woodType), info.blockState), info.tileentityData);
|
||||
}else if(LECTERNS.containsValue(info.blockState.getBlock())){
|
||||
return new Template.BlockInfo(info.pos, BlockUtils.copyState(LECTERNS.get(woodType), info.blockState), info.tileentityData);
|
||||
}
|
||||
|
||||
return info;
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
package electroblob.wizardry.worldgen;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.integration.antiqueatlas.WizardryAntiqueAtlasIntegration;
|
||||
import electroblob.wizardry.tileentity.TileEntityBookshelf;
|
||||
import electroblob.wizardry.util.BlockUtils;
|
||||
import net.minecraft.block.BlockPlanks;
|
||||
import net.minecraft.block.BlockStoneBrick;
|
||||
import net.minecraft.block.BlockStoneSlab;
|
||||
import net.minecraft.block.BlockStoneSlab.EnumType;
|
||||
import net.minecraft.init.Blocks;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
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 net.minecraftforge.common.BiomeDictionary.Type;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class WorldGenLibraryRuins extends WorldGenSurfaceStructure {
|
||||
|
||||
private static final List<Type> BIOME_TYPES = ImmutableList.of(Type.FOREST, Type.JUNGLE, Type.SWAMP);
|
||||
|
||||
@Override
|
||||
public String getStructureName(){
|
||||
return "library_ruins";
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getRandomSeedModifier(){
|
||||
return 10149523L;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canGenerate(Random random, World world, int chunkX, int chunkZ){
|
||||
return ArrayUtils.contains(Wizardry.settings.libraryDimensions, world.provider.getDimension())
|
||||
&& BiomeDictionary.getTypes(world.getBiome(new BlockPos(chunkX * 16 + 8, 0, chunkZ * 16 + 8)))
|
||||
.stream().anyMatch(BIOME_TYPES::contains)
|
||||
&& Wizardry.settings.libraryRarity > 0 && random.nextInt(Wizardry.settings.libraryRarity) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getStructureFile(Random random){
|
||||
return Wizardry.settings.libraryFiles[random.nextInt(Wizardry.settings.libraryFiles.length)];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void spawnStructure(Random random, World world, BlockPos origin, Template template, PlacementSettings settings, ResourceLocation structureFile){
|
||||
|
||||
final Biome biome = world.getBiome(origin);
|
||||
final float stoneBrickChance = random.nextFloat();
|
||||
final float mossiness = 0.4f;
|
||||
final BlockPlanks.EnumType woodType = BlockUtils.getBiomeWoodVariant(biome);
|
||||
|
||||
ITemplateProcessor processor = new MultiTemplateProcessor(true,
|
||||
// Cobblestone/stone brick
|
||||
(w, p, i) -> {
|
||||
if(w.rand.nextFloat() > stoneBrickChance){
|
||||
// Behold, three different ways of doing the same thing, because this is pre-flattening!
|
||||
// Also, stone bricks are about the least consistently-named thing in the entire game, so yay
|
||||
if(i.blockState.getBlock() == Blocks.COBBLESTONE){
|
||||
return new Template.BlockInfo(i.pos, Blocks.STONEBRICK.getDefaultState(), i.tileentityData);
|
||||
}else if(i.blockState.getBlock() == Blocks.STONE_SLAB
|
||||
&& i.blockState.getValue(BlockStoneSlab.VARIANT) == EnumType.COBBLESTONE){
|
||||
return new Template.BlockInfo(i.pos, i.blockState.withProperty(BlockStoneSlab.VARIANT, EnumType.SMOOTHBRICK), i.tileentityData);
|
||||
}else if(i.blockState.getBlock() == Blocks.STONE_STAIRS){ // "Stone" stairs are actually cobblestone
|
||||
return new Template.BlockInfo(i.pos, BlockUtils.copyState(Blocks.STONE_BRICK_STAIRS, i.blockState), i.tileentityData);
|
||||
}
|
||||
}
|
||||
return i;
|
||||
},
|
||||
// Wood type
|
||||
new WoodTypeTemplateProcessor(woodType),
|
||||
// Mossifier
|
||||
new MossifierTemplateProcessor(mossiness, 0.04f, origin.getY() + 1),
|
||||
// Stone brick smasher-upper
|
||||
(w, p, i) -> i.blockState.getBlock() == Blocks.STONEBRICK && w.rand.nextFloat() < 0.1f ?
|
||||
new Template.BlockInfo(i.pos, Blocks.STONEBRICK.getDefaultState().withProperty(
|
||||
BlockStoneBrick.VARIANT, BlockStoneBrick.EnumType.CRACKED), i.tileentityData) : i,
|
||||
// Bookshelf marker
|
||||
(w, p, i) -> {
|
||||
TileEntityBookshelf.markAsNatural(i.tileentityData);
|
||||
return i;
|
||||
}
|
||||
);
|
||||
|
||||
template.addBlocksToWorld(world, origin, processor, settings, 2);
|
||||
|
||||
WizardryAntiqueAtlasIntegration.markLibrary(world, origin.getX(), origin.getZ());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,134 +2,48 @@ package electroblob.wizardry.worldgen;
|
||||
|
||||
import com.google.common.math.Quantiles;
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.registry.WizardryAdvancementTriggers;
|
||||
import electroblob.wizardry.util.BlockUtils;
|
||||
import electroblob.wizardry.util.NBTExtras;
|
||||
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.util.math.AxisAlignedBB;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
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.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.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/** 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 {
|
||||
/**
|
||||
* Base class for all wizardry's above-ground structures, which handles various shared functions such as calculating
|
||||
* the median ground level and removing floating trees. This class implements
|
||||
* {@link WorldGenWizardryStructure#attemptPosition(Template, PlacementSettings, Random, World, int, int, String)}
|
||||
* but leaves the rest of the abstract methods to be implemented by subclasses.
|
||||
*
|
||||
* @author Electroblob
|
||||
* @since Wizardry 4.2
|
||||
*/
|
||||
public abstract class WorldGenSurfaceStructure extends WorldGenWizardryStructure {
|
||||
|
||||
/** 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
|
||||
* @param structureFile The name of the structure file, used for error messages.
|
||||
* @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.
|
||||
*/
|
||||
// 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.
|
||||
@Override
|
||||
@Nullable
|
||||
protected BlockPos findValidPosition(Template template, PlacementSettings settings, Random random, World world,
|
||||
int chunkX, int chunkZ, String structureFile){
|
||||
protected BlockPos attemptPosition(Template template, PlacementSettings settings, Random random, World world,
|
||||
int chunkX, int chunkZ, String structureFile){
|
||||
|
||||
// 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
|
||||
@@ -138,10 +52,6 @@ public abstract class WorldGenSurfaceStructure implements IWorldGenerator {
|
||||
|
||||
BlockPos size = template.transformedSize(settings.getRotation());
|
||||
|
||||
if(size.getX() == 0 || size.getY() == 0 || size.getZ() == 0){
|
||||
Wizardry.logger.warn("Structure template file {} is missing or empty! If you're trying to disable structure spawning, pointing to a non-existent location is NOT the correct way to do so; use the structure dimension lists instead.", structureFile);
|
||||
}
|
||||
|
||||
// Estimate a starting height for searching for the floor
|
||||
BlockPos centre = world.getTopSolidOrLiquidBlock(new BlockPos(origin.add(size.getX()/2, 0, size.getZ()/2)));
|
||||
// Check if we're at the top of the world, and if so just randomise the y pos (accounts for 'cavern' dimensions)
|
||||
@@ -193,125 +103,8 @@ public abstract class WorldGenSurfaceStructure implements IWorldGenerator {
|
||||
}
|
||||
|
||||
@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, structureFile.toString());
|
||||
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, random);
|
||||
|
||||
structureMap.put(ChunkPos.asLong(origin.getX() >> 4, origin.getZ() >> 4), settings.getBoundingBox());
|
||||
|
||||
NBTTagCompound tag = new NBTTagCompound();
|
||||
tag.setInteger("ChunkX", chunkX);
|
||||
tag.setInteger("ChunkZ", chunkZ);
|
||||
NBTExtras.storeTagSafely(tag, "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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
protected void postGenerate(Random random, World world, PlacementSettings settings){
|
||||
if(!Wizardry.settings.fastWorldgen) removeFloatingTrees(world, settings.getBoundingBox(), random);
|
||||
}
|
||||
|
||||
/** Finds and removes any floating bits of tree in and above the given structure bounding box. */
|
||||
@@ -374,85 +167,4 @@ public abstract class WorldGenSurfaceStructure implements IWorldGenerator {
|
||||
|
||||
}
|
||||
|
||||
/** 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,104 @@
|
||||
package electroblob.wizardry.worldgen;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.integration.antiqueatlas.WizardryAntiqueAtlasIntegration;
|
||||
import electroblob.wizardry.registry.WizardryBlocks;
|
||||
import electroblob.wizardry.tileentity.TileEntityBookshelf;
|
||||
import electroblob.wizardry.util.BlockUtils;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockPlanks;
|
||||
import net.minecraft.block.BlockStoneBrick;
|
||||
import net.minecraft.block.BlockStoneSlab;
|
||||
import net.minecraft.block.BlockStoneSlab.EnumType;
|
||||
import net.minecraft.init.Blocks;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
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 org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
||||
public class WorldGenUndergroundLibraryRuins extends WorldGenUndergroundStructure {
|
||||
|
||||
private final Set<Block> nonReplaceableBlocks;
|
||||
|
||||
public WorldGenUndergroundLibraryRuins(){
|
||||
nonReplaceableBlocks = ImmutableSet.of(WizardryBlocks.imbuement_altar, WizardryBlocks.receptacle,
|
||||
WizardryBlocks.oak_bookshelf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStructureName(){
|
||||
return "underground_library_ruins";
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getRandomSeedModifier(){
|
||||
return 13012834L;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getStructureFile(Random random){
|
||||
return Wizardry.settings.undergroundLibraryFiles[random.nextInt(Wizardry.settings.undergroundLibraryFiles.length)];
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canGenerate(Random random, World world, int chunkX, int chunkZ){
|
||||
return ArrayUtils.contains(Wizardry.settings.libraryDimensions, world.provider.getDimension())
|
||||
&& Wizardry.settings.libraryRarity > 0 && random.nextInt(Wizardry.settings.libraryRarity) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void spawnStructure(Random random, World world, BlockPos origin, Template template, PlacementSettings settings, ResourceLocation structureFile){
|
||||
|
||||
final Biome biome = world.getBiome(origin);
|
||||
final float stoneBrickChance = random.nextFloat();
|
||||
final float mossiness = 0.6f; // Underground is a mossier place!
|
||||
final BlockPlanks.EnumType woodType = BlockUtils.getBiomeWoodVariant(biome);
|
||||
|
||||
ITemplateProcessor processor = new MultiTemplateProcessor(true,
|
||||
// Erase walls and ceilings where caves were
|
||||
(w, p, i) -> w.isAirBlock(p) && !nonReplaceableBlocks.contains(i.blockState.getBlock()) ? null : i,
|
||||
// Cobblestone/stone brick
|
||||
(w, p, i) -> {
|
||||
if(w.rand.nextFloat() > stoneBrickChance){
|
||||
// Behold, three different ways of doing the same thing, because this is pre-flattening!
|
||||
// Also, stone bricks are about the least consistently-named thing in the entire game, so yay
|
||||
if(i.blockState.getBlock() == Blocks.COBBLESTONE){
|
||||
return new Template.BlockInfo(i.pos, Blocks.STONEBRICK.getDefaultState(), i.tileentityData);
|
||||
}else if(i.blockState.getBlock() == Blocks.STONE_SLAB
|
||||
&& i.blockState.getValue(BlockStoneSlab.VARIANT) == EnumType.COBBLESTONE){
|
||||
return new Template.BlockInfo(i.pos, i.blockState.withProperty(BlockStoneSlab.VARIANT, EnumType.SMOOTHBRICK), i.tileentityData);
|
||||
}else if(i.blockState.getBlock() == Blocks.STONE_STAIRS){ // "Stone" stairs are actually cobblestone
|
||||
return new Template.BlockInfo(i.pos, BlockUtils.copyState(Blocks.STONE_BRICK_STAIRS, i.blockState), i.tileentityData);
|
||||
}
|
||||
}
|
||||
return i;
|
||||
},
|
||||
// Wood type
|
||||
new WoodTypeTemplateProcessor(woodType),
|
||||
// Mossifier
|
||||
new MossifierTemplateProcessor(mossiness, 0.04f, origin.getY() + 1),
|
||||
// Stone brick smasher-upper
|
||||
(w, p, i) -> i.blockState.getBlock() == Blocks.STONEBRICK && w.rand.nextFloat() < 0.1f ?
|
||||
new Template.BlockInfo(i.pos, Blocks.STONEBRICK.getDefaultState().withProperty(
|
||||
BlockStoneBrick.VARIANT, BlockStoneBrick.EnumType.CRACKED), i.tileentityData) : i,
|
||||
// Bookshelf marker
|
||||
(w, p, i) -> {
|
||||
TileEntityBookshelf.markAsNatural(i.tileentityData);
|
||||
return i;
|
||||
}
|
||||
);
|
||||
|
||||
template.addBlocksToWorld(world, origin, processor, settings, 2);
|
||||
|
||||
WizardryAntiqueAtlasIntegration.markLibrary(world, origin.getX(), origin.getZ());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package electroblob.wizardry.worldgen;
|
||||
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.gen.structure.template.PlacementSettings;
|
||||
import net.minecraft.world.gen.structure.template.Template;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Base class for all wizardry's underground structures, which handles finding a location with a cave entrance (based
|
||||
* on {@code WorldGenDungeons}, with some tweaks). This class implements
|
||||
* {@link WorldGenWizardryStructure#attemptPosition(Template, PlacementSettings, Random, World, int, int, String)}
|
||||
* but leaves the rest of the abstract methods to be implemented by subclasses.
|
||||
*
|
||||
* @author Electroblob
|
||||
* @since Wizardry 4.3
|
||||
*/
|
||||
public abstract class WorldGenUndergroundStructure extends WorldGenWizardryStructure {
|
||||
|
||||
private static final int MAX_ENTRANCES = 5;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected BlockPos attemptPosition(Template template, PlacementSettings settings, Random random, World world, int chunkX, int chunkZ, String structureFile){
|
||||
|
||||
BlockPos origin = new BlockPos(8 + (chunkX << 4) + random.nextInt(16), 20 + random.nextInt(40), 8 + (chunkZ << 4) + random.nextInt(16));
|
||||
|
||||
BlockPos size = template.transformedSize(settings.getRotation());
|
||||
BlockPos corner = origin.add(size.getX(), 1, size.getZ()); // Need not iterate through everything
|
||||
|
||||
// Criteria for a valid position:
|
||||
// - There must be at least one entrance at floor level that a player can fit through, and no more than 5
|
||||
// - All blocks that the floor will replace must be solid
|
||||
// - Unlike with dungeons, there CAN be holes in the ceiling (mainly because it's not flat!)
|
||||
|
||||
int entrances = 0;
|
||||
|
||||
for(BlockPos pos : BlockPos.getAllInBox(origin, corner)){
|
||||
|
||||
// Assume the bottom layer of the structure to be the floor
|
||||
if(pos.getY() == origin.getY() && !world.getBlockState(pos).getMaterial().isSolid()){
|
||||
return null; // Can't generate with space underneath
|
||||
}
|
||||
|
||||
if((pos.getX() == origin.getX() || pos.getX() == corner.getX() || pos.getZ() == origin.getZ() || pos.getZ() == corner.getZ())
|
||||
&& pos.getY() == origin.getY() + 1 && world.isAirBlock(pos) && world.isAirBlock(pos.up())){
|
||||
if(entrances++ > MAX_ENTRANCES) return null;
|
||||
}
|
||||
}
|
||||
|
||||
if(entrances == 0) return null; // No way in!
|
||||
|
||||
return origin;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,11 +5,11 @@ 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.BlockUtils;
|
||||
import electroblob.wizardry.util.GeometryUtils;
|
||||
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;
|
||||
@@ -37,11 +37,11 @@ public class WorldGenWizardTower extends WorldGenSurfaceStructure {
|
||||
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;
|
||||
private final Map<BiomeDictionary.Type, IBlockState> specialWallBlocks;
|
||||
|
||||
public WorldGenWizardTower(){
|
||||
// These are initialised here because it's a convenient point after the blocks are registered
|
||||
SPECIAL_WALL_BLOCKS = ImmutableMap.of(
|
||||
specialWallBlocks = ImmutableMap.of(
|
||||
BiomeDictionary.Type.MESA, Blocks.RED_SANDSTONE.getDefaultState(),
|
||||
BiomeDictionary.Type.MOUNTAIN, Blocks.STONEBRICK.getDefaultState(),
|
||||
BiomeDictionary.Type.NETHER, Blocks.NETHER_BRICK.getDefaultState(),
|
||||
@@ -78,11 +78,11 @@ public class WorldGenWizardTower extends WorldGenSurfaceStructure {
|
||||
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 IBlockState wallMaterial = specialWallBlocks.keySet().stream().filter(t -> BiomeDictionary.hasType(biome, t))
|
||||
.findFirst().map(specialWallBlocks::get).orElse(Blocks.COBBLESTONE.getDefaultState());
|
||||
|
||||
final float mossiness = getBiomeMossiness(biome);
|
||||
final BlockPlanks.EnumType woodType = getBiomeWoodVariant(biome);
|
||||
final BlockPlanks.EnumType woodType = BlockUtils.getBiomeWoodVariant(biome);
|
||||
|
||||
final Set<BlockPos> blocksPlaced = new HashSet<>();
|
||||
|
||||
@@ -150,16 +150,4 @@ public class WorldGenWizardTower extends WorldGenSurfaceStructure {
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,350 @@
|
||||
package electroblob.wizardry.worldgen;
|
||||
|
||||
import electroblob.wizardry.Wizardry;
|
||||
import electroblob.wizardry.registry.WizardryAdvancementTriggers;
|
||||
import electroblob.wizardry.util.NBTExtras;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.nbt.NBTBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.Mirror;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.Rotation;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.ChunkPos;
|
||||
import net.minecraft.util.math.Vec3i;
|
||||
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.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.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Base structure generation class which handles code common to all wizardry's structures. This class was generalised
|
||||
* from {@link WorldGenSurfaceStructure} in Wizardry 4.3 to allow underground structures to share much of the code.
|
||||
*
|
||||
* @author Electroblob
|
||||
* @since Wizardry 4.3
|
||||
*/
|
||||
@Mod.EventBusSubscriber
|
||||
public abstract class WorldGenWizardryStructure implements IWorldGenerator {
|
||||
|
||||
/** Static map used to store all structure generators for the purpose of advancements. */
|
||||
private static final Map<String, WorldGenWizardryStructure> 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 WorldGenWizardryStructure(){
|
||||
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();
|
||||
|
||||
/** 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();
|
||||
|
||||
/** 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};
|
||||
}
|
||||
|
||||
/** 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 WorldGenWizardryStructure#spawnStructure(Random, World, BlockPos, Template, PlacementSettings, ResourceLocation)} */
|
||||
public abstract boolean canGenerate(Random random, World world, int chunkX, int chunkZ);
|
||||
|
||||
/**
|
||||
* Picks a random position within the given chunk at which to generate the given template, returning null if that
|
||||
* position is found to be unsuitable. In an effort to make structure rarity more uniform, they now get a number of
|
||||
* attempts 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.), or otherwise satisfying spawning conditions. This method performs a
|
||||
* single attempt at finding a suitable position.
|
||||
*
|
||||
* @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
|
||||
* @param structureFile The name of the structure file, used for error messages.
|
||||
* @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.
|
||||
*/
|
||||
// It may seem more logical for this class to generate a position and then have subclasses only check if it is
|
||||
// valid, but different types of structure pick positions differently so that has to be left to subclasses
|
||||
@Nullable
|
||||
protected abstract BlockPos attemptPosition(Template template, PlacementSettings settings, Random random, World world,
|
||||
int chunkX, int chunkZ, String structureFile);
|
||||
|
||||
/**
|
||||
* Spawns the structure at the given origin with the given placement settings. This method should handle template
|
||||
* processors and the actual placing of the blocks in the world.
|
||||
* @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, already set to a valid position and adjusted
|
||||
* so that it is the NW corner (-X, -Z) regardless of rotation/mirror settings
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* Called after the structure is generated to perform any post-generation extras (optional). For example,
|
||||
* {@link WorldGenSurfaceStructure} uses this to clean up floating trees.
|
||||
* @param random The random number generator that was used to generate the structure
|
||||
* @param world The world in which the structure was generated
|
||||
* @param settings The placement settings that the structure was generated with (including its bounding box)
|
||||
*/
|
||||
protected void postGenerate(Random random, World world, PlacementSettings settings){}
|
||||
|
||||
@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);
|
||||
|
||||
BlockPos size = template.getSize();
|
||||
|
||||
if(size.getX() == 0 || size.getY() == 0 || size.getZ() == 0){
|
||||
Wizardry.logger.warn("Structure template file {} is missing or empty! If you're trying to disable structure spawning, pointing to a non-existent location is NOT the correct way to do so; use the structure dimension lists instead.", 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 = attemptPosition(template, settings, random, world, chunkX, chunkZ, structureFile.toString());
|
||||
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(WorldGenWizardryStructure 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.
|
||||
// TODO: Actually this may not truly minimise cascading, hmmm
|
||||
origin = template.getZeroPositionWithTransform(origin, settings.getMirror(), settings.getRotation());
|
||||
|
||||
spawnStructure(random, world, origin, template, settings, structureFile);
|
||||
|
||||
postGenerate(random, world, settings);
|
||||
|
||||
structureMap.put(ChunkPos.asLong(origin.getX() >> 4, origin.getZ() >> 4), settings.getBoundingBox());
|
||||
|
||||
NBTTagCompound tag = new NBTTagCompound();
|
||||
tag.setInteger("ChunkX", chunkX);
|
||||
tag.setInteger("ChunkZ", chunkZ);
|
||||
NBTExtras.storeTagSafely(tag, "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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 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 WorldGenWizardryStructure 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user