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:
@@ -134,6 +134,20 @@ public final class Settings {
|
||||
new ResourceLocation(Wizardry.MODID, "shrine_5"),
|
||||
new ResourceLocation(Wizardry.MODID, "shrine_6"),
|
||||
new ResourceLocation(Wizardry.MODID, "shrine_7")};
|
||||
/** <b>[Server-only]</b> List of dimension ids in which to generate library ruins. */
|
||||
public int[] libraryDimensions = {0};
|
||||
/** <b>[Server-only]</b> The rarity of library ruins, used by the world generator. Larger numbers are rarer. */
|
||||
public int libraryRarity = 900;
|
||||
/** <b>[Server-only]</b> List of structure file locations for surface library ruins. */
|
||||
public ResourceLocation[] libraryFiles = {new ResourceLocation(Wizardry.MODID, "library_ruins_0"),
|
||||
new ResourceLocation(Wizardry.MODID, "library_ruins_1"),
|
||||
new ResourceLocation(Wizardry.MODID, "library_ruins_2"),
|
||||
new ResourceLocation(Wizardry.MODID, "library_ruins_3")};
|
||||
/** <b>[Server-only]</b> List of structure file locations for underground library ruins. */
|
||||
public ResourceLocation[] undergroundLibraryFiles = {new ResourceLocation(Wizardry.MODID, "underground_library_ruins_0"),
|
||||
new ResourceLocation(Wizardry.MODID, "underground_library_ruins_1"),
|
||||
new ResourceLocation(Wizardry.MODID, "underground_library_ruins_2"),
|
||||
new ResourceLocation(Wizardry.MODID, "underground_library_ruins_3")};
|
||||
/** <b>[Server-only]</b> List of solid blocks (usually trees) which are ignored by the structure generators. */
|
||||
public Pair<ResourceLocation, Short>[] treeBlocks = parseItemMetaStrings(DEFAULT_TREE_BLOCKS);
|
||||
/** <b>[Server-only]</b> The chance for wizard towers to generate with an evil wizard and chest inside. */
|
||||
@@ -254,12 +268,15 @@ public final class Settings {
|
||||
public boolean jeiIntegration = true;
|
||||
/** <b>[Server-only]</b> Whether Antique Atlas integration features are enabled. */
|
||||
public boolean antiqueAtlasIntegration = true;
|
||||
// These are server-only because that's where markers are stored
|
||||
/** <b>[Server-only]</b> Whether global markers for wizard towers are added to antique atlases. */
|
||||
public boolean autoTowerMarkers = true;
|
||||
/** <b>[Server-only]</b> Whether global markers for obelisks are added to antique atlases. */
|
||||
public boolean autoObeliskMarkers = true;
|
||||
/** <b>[Server-only]</b> Whether global markers for shrines are added to antique atlases. */
|
||||
public boolean autoShrineMarkers = true;
|
||||
/** <b>[Server-only]</b> Whether global markers for library ruins are added to antique atlases. */
|
||||
public boolean autoLibraryMarkers = true;
|
||||
|
||||
// Synchronised settings. These settings affect both client-side AND server-side code. Changing these locally
|
||||
// only has an effect if the local game is the host, i.e. a dedicated server, a LAN host or a singleplayer world.
|
||||
@@ -920,6 +937,33 @@ public final class Settings {
|
||||
shrineFiles = getResourceLocationList(property);
|
||||
propOrder.add(property.getName());
|
||||
|
||||
property = config.get(WORLDGEN_CATEGORY, "libraryDimensions", new int[]{0}, "List of dimension ids in which library ruins will generate. Remove all dimensions to disable library ruins completely.");
|
||||
property.setLanguageKey("config." + Wizardry.MODID + ".library_dimensions");
|
||||
property.setRequiresWorldRestart(true);
|
||||
libraryDimensions = property.getIntList();
|
||||
propOrder.add(property.getName());
|
||||
|
||||
property = config.get(WORLDGEN_CATEGORY, "libraryRarity", 900, "Rarity of library ruins. 1 in this many chunks will contain a library ruin, meaning higher numbers are rarer.", 20, 5000);
|
||||
property.setLanguageKey("config." + Wizardry.MODID + ".library_rarity");
|
||||
property.setRequiresWorldRestart(true);
|
||||
Wizardry.proxy.setToNumberSliderEntry(property);
|
||||
libraryRarity = property.getInt();
|
||||
propOrder.add(property.getName());
|
||||
|
||||
property = config.get(WORLDGEN_CATEGORY, "libraryFiles", new String[]{Wizardry.MODID + ":library_ruins_0", Wizardry.MODID + ":library_ruins_1", Wizardry.MODID + ":library_ruins_2", Wizardry.MODID + ":library_ruins_3"},
|
||||
"List of structure file locations for surface library ruins. One of these files will be randomly selected each time a surface library ruin is generated. File locations are of the format [mod id]:[filename], which refers to the file assets/[mod id]/structures/[filename].nbt. Duplicate entries are permitted, allowing for simple weighting without duplicating the structure files themselves. This list should not be empty; to disable library ruins, use the library dimensions setting.");
|
||||
property.setLanguageKey("config." + Wizardry.MODID + ".library_files");
|
||||
property.setRequiresWorldRestart(true);
|
||||
libraryFiles = getResourceLocationList(property);
|
||||
propOrder.add(property.getName());
|
||||
|
||||
property = config.get(WORLDGEN_CATEGORY, "undergroundLibraryFiles", new String[]{Wizardry.MODID + ":underground_library_ruins_0", Wizardry.MODID + ":underground_library_ruins_1", Wizardry.MODID + ":underground_library_ruins_2", Wizardry.MODID + ":underground_library_ruins_3"},
|
||||
"List of structure file locations for underground library ruins. One of these files will be randomly selected each time an underground library ruin is generated. File locations are of the format [mod id]:[filename], which refers to the file assets/[mod id]/structures/[filename].nbt. Duplicate entries are permitted, allowing for simple weighting without duplicating the structure files themselves. This list should not be empty; to disable library ruins, use the library dimensions setting.");
|
||||
property.setLanguageKey("config." + Wizardry.MODID + ".underground_library_files");
|
||||
property.setRequiresWorldRestart(true);
|
||||
undergroundLibraryFiles = getResourceLocationList(property);
|
||||
propOrder.add(property.getName());
|
||||
|
||||
property = config.get(WORLDGEN_CATEGORY, "treeBlocks", DEFAULT_TREE_BLOCKS, "List of registry names of blocks which can be overwritten by wizardry's structure generators, affecting both fast and fancy structure generation. Most tree blocks and other foliage should work automatically, but those that don't can be added manually here. Block names are not case sensitive. For mod blocks, prefix with the mod ID (e.g. dynamictrees:oakbranch).");
|
||||
property.setLanguageKey("config." + Wizardry.MODID + ".tree_blocks");
|
||||
property.setRequiresWorldRestart(true);
|
||||
@@ -1234,6 +1278,14 @@ public final class Settings {
|
||||
autoShrineMarkers = property.getBoolean();
|
||||
propOrder.add(property.getName());
|
||||
|
||||
property = config.get(COMPATIBILITY_CATEGORY, "autoPlaceLibraryMarkers", true,
|
||||
"Controls whether wizardry automatically places antique atlas markers at the locations of library ruins.");
|
||||
property.setLanguageKey("config." + Wizardry.MODID + ".auto_place_library_markers");
|
||||
property.setRequiresMcRestart(true);
|
||||
Wizardry.proxy.setToNamedBooleanEntry(property);
|
||||
autoLibraryMarkers = property.getBoolean();
|
||||
propOrder.add(property.getName());
|
||||
|
||||
config.setCategoryPropertyOrder(COMPATIBILITY_CATEGORY, propOrder);
|
||||
|
||||
}
|
||||
|
||||
@@ -154,6 +154,8 @@ public class Wizardry {
|
||||
GameRegistry.registerWorldGenerator(new WorldGenWizardTower(), 20);
|
||||
GameRegistry.registerWorldGenerator(new WorldGenObelisk(), 20);
|
||||
GameRegistry.registerWorldGenerator(new WorldGenShrine(), 20);
|
||||
GameRegistry.registerWorldGenerator(new WorldGenLibraryRuins(), 20);
|
||||
GameRegistry.registerWorldGenerator(new WorldGenUndergroundLibraryRuins(), 20);
|
||||
|
||||
// This is for the config change and missing mappings events
|
||||
MinecraftForge.EVENT_BUS.register(instance); // Since there's already an instance we might as well use it
|
||||
|
||||
@@ -5,7 +5,7 @@ import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonObject;
|
||||
import electroblob.wizardry.worldgen.WorldGenSurfaceStructure;
|
||||
import electroblob.wizardry.worldgen.WorldGenWizardryStructure;
|
||||
import net.minecraft.advancements.ICriterionTrigger;
|
||||
import net.minecraft.advancements.PlayerAdvancements;
|
||||
import net.minecraft.advancements.critereon.AbstractCriterionInstance;
|
||||
@@ -77,11 +77,11 @@ public class StructureTrigger implements ICriterionTrigger<StructureTrigger.Inst
|
||||
|
||||
public static class Instance extends AbstractCriterionInstance {
|
||||
|
||||
private final WorldGenSurfaceStructure structureType;
|
||||
private final WorldGenWizardryStructure structureType;
|
||||
|
||||
public Instance(ResourceLocation criterionIn, String name){
|
||||
super(criterionIn);
|
||||
this.structureType = WorldGenSurfaceStructure.byName(name);
|
||||
this.structureType = WorldGenWizardryStructure.byName(name);
|
||||
}
|
||||
|
||||
public boolean test(WorldServer world, double x, double y, double z){
|
||||
|
||||
@@ -84,6 +84,18 @@ public class BlockBookshelf extends BlockHorizontal implements ITileEntityProvid
|
||||
return this.getDefaultState().withProperty(FACING, placer.getHorizontalFacing().getOpposite());
|
||||
}
|
||||
|
||||
// Why on earth are these two not in BlockHorizontal?
|
||||
|
||||
@Override
|
||||
public IBlockState withRotation(IBlockState state, Rotation rotation){
|
||||
return state.withProperty(FACING, rotation.rotate(state.getValue(FACING)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBlockState withMirror(IBlockState state, Mirror mirror){
|
||||
return state.withRotation(mirror.toRotation(state.getValue(FACING)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlockAdded(World worldIn, BlockPos pos, IBlockState state){
|
||||
super.onBlockAdded(worldIn, pos, state);
|
||||
|
||||
@@ -18,6 +18,8 @@ import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraft.util.EnumHand;
|
||||
import net.minecraft.util.Mirror;
|
||||
import net.minecraft.util.Rotation;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.IBlockAccess;
|
||||
import net.minecraft.world.World;
|
||||
@@ -103,6 +105,16 @@ public class BlockLectern extends BlockHorizontal implements ITileEntityProvider
|
||||
return this.getDefaultState().withProperty(FACING, placer.getHorizontalFacing().getOpposite());
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBlockState withRotation(IBlockState state, Rotation rotation){
|
||||
return state.withProperty(FACING, rotation.rotate(state.getValue(FACING)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBlockState withMirror(IBlockState state, Mirror mirror){
|
||||
return state.withRotation(mirror.toRotation(state.getValue(FACING)));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public TileEntity createNewTileEntity(World world, int meta){
|
||||
|
||||
+10
@@ -29,6 +29,7 @@ public class WizardryAntiqueAtlasIntegration {
|
||||
private static final ResourceLocation TOWER_MARKER = new ResourceLocation(Wizardry.MODID, "wizard_tower");
|
||||
private static final ResourceLocation SHRINE_MARKER = new ResourceLocation(Wizardry.MODID, "shrine");
|
||||
private static final ResourceLocation OBELISK_MARKER = new ResourceLocation(Wizardry.MODID, "obelisk");
|
||||
private static final ResourceLocation LIBRARY_MARKER = new ResourceLocation(Wizardry.MODID, "library_ruins");
|
||||
|
||||
private static boolean antiqueAtlasLoaded;
|
||||
|
||||
@@ -65,6 +66,14 @@ public class WizardryAntiqueAtlasIntegration {
|
||||
}
|
||||
}
|
||||
|
||||
/** Places a global library ruins marker in all antique atlases at the given coordinates in the given world if
|
||||
* {@link electroblob.wizardry.Settings#autoLibraryMarkers} is enabled. Server side only! */
|
||||
public static void markLibrary(World world, int x, int z){
|
||||
if(enabled() && Wizardry.settings.autoLibraryMarkers){ // TODO: More granular settings for above/below ground
|
||||
AtlasAPI.getMarkerAPI().putGlobalMarker(world, false, LIBRARY_MARKER.toString(), "integration.antiqueatlas.marker." + LIBRARY_MARKER.toString().replace(':', '.'), x, z);
|
||||
}
|
||||
}
|
||||
|
||||
/** Registers the marker icons with Antique Atlas. Client side only! */
|
||||
public static void registerMarkers(){
|
||||
|
||||
@@ -73,6 +82,7 @@ public class WizardryAntiqueAtlasIntegration {
|
||||
AtlasAPI.getMarkerAPI().registerMarker(new MarkerType(TOWER_MARKER, new ResourceLocation(Wizardry.MODID, "textures/integration/antiqueatlas/wizard_tower.png")));
|
||||
AtlasAPI.getMarkerAPI().registerMarker(new MarkerType(SHRINE_MARKER, new ResourceLocation(Wizardry.MODID, "textures/integration/antiqueatlas/shrine.png")));
|
||||
AtlasAPI.getMarkerAPI().registerMarker(new MarkerType(OBELISK_MARKER, new ResourceLocation(Wizardry.MODID, "textures/integration/antiqueatlas/obelisk.png")));
|
||||
AtlasAPI.getMarkerAPI().registerMarker(new MarkerType(LIBRARY_MARKER, new ResourceLocation(Wizardry.MODID, "textures/integration/antiqueatlas/library_ruins.png")));
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
|
||||
@@ -12,6 +12,7 @@ import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.nbt.NBTTagList;
|
||||
import net.minecraft.network.NetworkManager;
|
||||
import net.minecraft.network.play.server.SPacketUpdateTileEntity;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.tileentity.TileEntityLockableLoot;
|
||||
import net.minecraft.util.ITickable;
|
||||
import net.minecraft.util.NonNullList;
|
||||
@@ -24,9 +25,21 @@ import javax.annotation.Nullable;
|
||||
|
||||
public class TileEntityBookshelf extends TileEntityLockableLoot implements ITickable {
|
||||
|
||||
/** NBT key for the boolean flag specifying if this bookshelf was generated naturally as part of a structure or not.
|
||||
* This flag is set via {@link TileEntityBookshelf#markAsNatural(NBTTagCompound)}. */
|
||||
private static final String NATURAL_NBT_KEY = "NaturallyGenerated";
|
||||
/** When a non-spectating player comes within this distance of a naturally-generated bookshelf, it will automatically
|
||||
* generate its loot if a loot table was set. This means the bookshelves do not incorrectly appear empty before a
|
||||
* player looks inside. */ // Kind of a trade-off between not seeing them appear and not triggering from miles away
|
||||
private static final int LOOT_GEN_DISTANCE = 32; // Nobody is likely to be looking from more than 32 blocks away
|
||||
|
||||
/** The inventory of the bookshelf. */
|
||||
private NonNullList<ItemStack> inventory;
|
||||
|
||||
/** Whether this bookshelf was generated naturally as part of a structure. This determines whether the loot is
|
||||
* automatically generated (and synced) when a non-spectating player comes within {@value LOOT_GEN_DISTANCE} blocks,
|
||||
* so the bookshelf doesn't look empty until opened (loot is generated with that player in the context). */
|
||||
private boolean natural;
|
||||
private boolean doNotSync;
|
||||
|
||||
public TileEntityBookshelf(){
|
||||
@@ -44,8 +57,19 @@ public class TileEntityBookshelf extends TileEntityLockableLoot implements ITick
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
|
||||
this.doNotSync = false;
|
||||
// Nothing here for now
|
||||
|
||||
// When a player gets near, generate the books so they can actually see them (if it was generated naturally)
|
||||
if(lootTable != null && natural){
|
||||
EntityPlayer player = world.getClosestPlayer(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5,
|
||||
LOOT_GEN_DISTANCE, false);
|
||||
if(player != null){
|
||||
natural = false; // It's a normal bookshelf now (unlikely to matter but you never know)
|
||||
fillWithLoot(player);
|
||||
sync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -101,6 +125,14 @@ public class TileEntityBookshelf extends TileEntityLockableLoot implements ITick
|
||||
this.markDirty();
|
||||
}
|
||||
|
||||
/** Sets the {@value NATURAL_NBT_KEY} flag to true in the given NBT tag compound, <b>if</b> the compound belongs to
|
||||
* a bookshelf tile entity (more specifically, if it has an "id" tag matching the bookshelf TE's registry name). */
|
||||
public static void markAsNatural(NBTTagCompound nbt){
|
||||
if(nbt != null && nbt.getString("id").equals(TileEntity.getKey(TileEntityBookshelf.class).toString())){
|
||||
nbt.setBoolean(NATURAL_NBT_KEY, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillWithLoot(@Nullable EntityPlayer player){
|
||||
if(world != null && world.getLootTableManager() != null) super.fillWithLoot(player); // IntelliJ is wrong, it can be null
|
||||
@@ -147,13 +179,15 @@ public class TileEntityBookshelf extends TileEntityLockableLoot implements ITick
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFromNBT(NBTTagCompound tagCompound){
|
||||
public void readFromNBT(NBTTagCompound nbt){
|
||||
|
||||
super.readFromNBT(tagCompound);
|
||||
super.readFromNBT(nbt);
|
||||
|
||||
if(!this.checkLootAndRead(tagCompound)){
|
||||
natural = nbt.getBoolean(NATURAL_NBT_KEY);
|
||||
|
||||
if(!this.checkLootAndRead(nbt)){
|
||||
// TODO: Replace with ItemStackHelper#loadAllItems
|
||||
NBTTagList tagList = tagCompound.getTagList("Inventory", NBT.TAG_COMPOUND);
|
||||
NBTTagList tagList = nbt.getTagList("Inventory", NBT.TAG_COMPOUND);
|
||||
|
||||
for(int i = 0; i < tagList.tagCount(); i++){
|
||||
NBTTagCompound tag = tagList.getCompoundTagAt(i);
|
||||
@@ -164,15 +198,18 @@ public class TileEntityBookshelf extends TileEntityLockableLoot implements ITick
|
||||
}
|
||||
}
|
||||
|
||||
if(tagCompound.hasKey("CustomName", NBT.TAG_STRING)) this.customName = tagCompound.getString("CustomName");
|
||||
if(nbt.hasKey("CustomName", NBT.TAG_STRING)) this.customName = nbt.getString("CustomName");
|
||||
}
|
||||
|
||||
@Override
|
||||
public NBTTagCompound writeToNBT(NBTTagCompound tagCompound){
|
||||
public NBTTagCompound writeToNBT(NBTTagCompound nbt){
|
||||
|
||||
super.writeToNBT(tagCompound);
|
||||
super.writeToNBT(nbt);
|
||||
|
||||
if(!this.checkLootAndWrite(tagCompound)){
|
||||
// Need to save this in case the block was generated but no players came near enough to trigger loot gen
|
||||
nbt.setBoolean(NATURAL_NBT_KEY, natural);
|
||||
|
||||
if(!this.checkLootAndWrite(nbt)){
|
||||
|
||||
// TODO: Replace with ItemStackHelper#saveAllItems
|
||||
|
||||
@@ -186,12 +223,12 @@ public class TileEntityBookshelf extends TileEntityLockableLoot implements ITick
|
||||
itemList.appendTag(tag);
|
||||
}
|
||||
|
||||
NBTExtras.storeTagSafely(tagCompound, "Inventory", itemList);
|
||||
NBTExtras.storeTagSafely(nbt, "Inventory", itemList);
|
||||
}
|
||||
|
||||
if(this.hasCustomName()) tagCompound.setString("CustomName", this.customName);
|
||||
if(this.hasCustomName()) nbt.setString("CustomName", this.customName);
|
||||
|
||||
return tagCompound;
|
||||
return nbt;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -7,6 +7,7 @@ import net.minecraft.block.material.Material;
|
||||
import net.minecraft.block.properties.IProperty;
|
||||
import net.minecraft.block.state.IBlockState;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.init.Biomes;
|
||||
import net.minecraft.init.Blocks;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
@@ -14,6 +15,8 @@ import net.minecraft.util.math.MathHelper;
|
||||
import net.minecraft.util.math.RayTraceResult;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.biome.Biome;
|
||||
import net.minecraftforge.common.BiomeDictionary;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
@@ -181,6 +184,25 @@ public final class BlockUtils {
|
||||
|| Settings.containsMetaBlock(Wizardry.settings.treeBlocks, world.getBlockState(pos));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the predominant wood type (see {@link net.minecraft.block.BlockPlanks.EnumType}) for the given biome, or
|
||||
* the default {@link net.minecraft.block.BlockPlanks.EnumType#OAK} for biomes with no trees. This is accurate for
|
||||
* most vanilla biomes
|
||||
* @param biome The biome to query
|
||||
* @return The wood type for the given biome
|
||||
*/
|
||||
public 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the position of one half of a double chest, returns the position of the other half of that double chest.
|
||||
* @param world The world in which the chest is located
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1442,6 +1442,14 @@ config.ebwizardry.shrine_rarity=Shrine Rarity
|
||||
config.ebwizardry.shrine_rarity.tooltip=Rarity of shrines. 1 in this many chunks will contain a shrine, meaning higher numbers are rarer.
|
||||
config.ebwizardry.shrine_files=Shrine Structure Files
|
||||
config.ebwizardry.shrine_files.tooltip=List of structure file locations for shrines. One of these files will be randomly selected each time a shrine is generated. File locations are of the format [mod id]\:[filename], which refers to the file assets/[mod id]/structures/[filename].nbt. Duplicate entries are permitted, allowing for simple weighting without duplicating the structure files themselves. This list should not be empty; to disable shrines, use the shrine dimensions setting.
|
||||
config.ebwizardry.library_dimensions=Library Ruin Dimensions
|
||||
config.ebwizardry.library_dimensions.tooltip=List of ids of dimensions in which library ruins will generate. Remove all dimensions to disable library ruins completely.
|
||||
config.ebwizardry.library_rarity=Library Ruin Rarity
|
||||
config.ebwizardry.library_rarity.tooltip=Rarity of library ruins. 1 in this many chunks will contain a shrine, meaning higher numbers are rarer.
|
||||
config.ebwizardry.library_files=Library Ruin Structure Files
|
||||
config.ebwizardry.library_files.tooltip=List of structure file locations for surface library ruins. One of these files will be randomly selected each time a library ruin is generated. File locations are of the format [mod id]\:[filename], which refers to the file assets/[mod id]/structures/[filename].nbt. Duplicate entries are permitted, allowing for simple weighting without duplicating the structure files themselves. This list should not be empty; to disable library ruins, use the library ruin dimensions setting.
|
||||
config.ebwizardry.underground_library_files=Underground Library Ruin Structure Files
|
||||
config.ebwizardry.underground_library_files.tooltip=List of structure file locations for underground library ruins. One of these files will be randomly selected each time a library ruin is generated. File locations are of the format [mod id]\:[filename], which refers to the file assets/[mod id]/structures/[filename].nbt. Duplicate entries are permitted, allowing for simple weighting without duplicating the structure files themselves. This list should not be empty; to disable library ruins, use the library ruin dimensions setting.
|
||||
config.ebwizardry.tree_blocks=Tree Blocks
|
||||
config.ebwizardry.tree_blocks.tooltip=List of registry names of blocks which can be overwritten by wizardry's structure generators, affecting both fast and fancy structure generation. Most tree blocks and other foliage should work automatically, but those that don't can be added manually here. Block names are not case sensitive. For mod blocks, prefix with the mod ID (e.g. dynamictrees:oakbranch).
|
||||
config.ebwizardry.ore_dimensions=Ore Dimensions
|
||||
@@ -1551,12 +1559,15 @@ config.ebwizardry.auto_place_obelisk_markers=Auto-Place Obelisk Markers
|
||||
config.ebwizardry.auto_place_obelisk_markers.tooltip=Controls whether wizardry automatically places antique atlas markers at the locations of obelisks.
|
||||
config.ebwizardry.auto_place_shrine_markers=Auto-Place Shrine Markers
|
||||
config.ebwizardry.auto_place_shrine_markers.tooltip=Controls whether wizardry automatically places antique atlas markers at the locations of shrines.
|
||||
config.ebwizardry.auto_place_library_markers=Auto-Place Library Markers
|
||||
config.ebwizardry.auto_place_library_markers.tooltip=Controls whether wizardry automatically places antique atlas markers at the locations of library ruins.
|
||||
|
||||
integration.jei.category.ebwizardry\:arcane_workbench=Arcane Workbench
|
||||
|
||||
integration.antiqueatlas.marker.ebwizardry.wizard_tower=Wizard Tower
|
||||
integration.antiqueatlas.marker.ebwizardry.obelisk=Obelisk
|
||||
integration.antiqueatlas.marker.ebwizardry.shrine=Shrine
|
||||
integration.antiqueatlas.marker.ebwizardry.library_ruins=Library Ruins
|
||||
|
||||
spell.ebwizardry\:magic_missile_festive=Magic Mistletoe
|
||||
spell.ebwizardry\:summon_snow_golem_festive=Summon Frosty The Snowman
|
||||
|
||||
@@ -1442,6 +1442,14 @@ config.ebwizardry.shrine_rarity=Shrine Rarity
|
||||
config.ebwizardry.shrine_rarity.tooltip=Rarity of shrines. 1 in this many chunks will contain a shrine, meaning higher numbers are rarer.
|
||||
config.ebwizardry.shrine_files=Shrine Structure Files
|
||||
config.ebwizardry.shrine_files.tooltip=List of structure file locations for shrines. One of these files will be randomly selected each time a shrine is generated. File locations are of the format [mod id]\:[filename], which refers to the file assets/[mod id]/structures/[filename].nbt. Duplicate entries are permitted, allowing for simple weighting without duplicating the structure files themselves. This list should not be empty; to disable shrines, use the shrine dimensions setting.
|
||||
config.ebwizardry.library_dimensions=Library Ruin Dimensions
|
||||
config.ebwizardry.library_dimensions.tooltip=List of ids of dimensions in which library ruins will generate. Remove all dimensions to disable library ruins completely.
|
||||
config.ebwizardry.library_rarity=Library Ruin Rarity
|
||||
config.ebwizardry.library_rarity.tooltip=Rarity of library ruins. 1 in this many chunks will contain a shrine, meaning higher numbers are rarer.
|
||||
config.ebwizardry.library_files=Library Ruin Structure Files
|
||||
config.ebwizardry.library_files.tooltip=List of structure file locations for surface library ruins. One of these files will be randomly selected each time a library ruin is generated. File locations are of the format [mod id]\:[filename], which refers to the file assets/[mod id]/structures/[filename].nbt. Duplicate entries are permitted, allowing for simple weighting without duplicating the structure files themselves. This list should not be empty; to disable library ruins, use the library ruin dimensions setting.
|
||||
config.ebwizardry.underground_library_files=Underground Library Ruin Structure Files
|
||||
config.ebwizardry.underground_library_files.tooltip=List of structure file locations for underground library ruins. One of these files will be randomly selected each time a library ruin is generated. File locations are of the format [mod id]\:[filename], which refers to the file assets/[mod id]/structures/[filename].nbt. Duplicate entries are permitted, allowing for simple weighting without duplicating the structure files themselves. This list should not be empty; to disable library ruins, use the library ruin dimensions setting.
|
||||
config.ebwizardry.tree_blocks=Tree Blocks
|
||||
config.ebwizardry.tree_blocks.tooltip=List of registry names of blocks which can be overwritten by wizardry's structure generators, affecting both fast and fancy structure generation. Most tree blocks and other foliage should work automatically, but those that don't can be added manually here. Block names are not case sensitive. For mod blocks, prefix with the mod ID (e.g. dynamictrees:oakbranch).
|
||||
config.ebwizardry.ore_dimensions=Ore Dimensions
|
||||
@@ -1551,12 +1559,15 @@ config.ebwizardry.auto_place_obelisk_markers=Auto-Place Obelisk Markers
|
||||
config.ebwizardry.auto_place_obelisk_markers.tooltip=Controls whether wizardry automatically places antique atlas markers at the locations of obelisks.
|
||||
config.ebwizardry.auto_place_shrine_markers=Auto-Place Shrine Markers
|
||||
config.ebwizardry.auto_place_shrine_markers.tooltip=Controls whether wizardry automatically places antique atlas markers at the locations of shrines.
|
||||
config.ebwizardry.auto_place_library_markers=Auto-Place Library Markers
|
||||
config.ebwizardry.auto_place_library_markers.tooltip=Controls whether wizardry automatically places antique atlas markers at the locations of library ruins.
|
||||
|
||||
integration.jei.category.ebwizardry\:arcane_workbench=Arcane Workbench
|
||||
|
||||
integration.antiqueatlas.marker.ebwizardry.wizard_tower=Wizard Tower
|
||||
integration.antiqueatlas.marker.ebwizardry.obelisk=Obelisk
|
||||
integration.antiqueatlas.marker.ebwizardry.shrine=Shrine
|
||||
integration.antiqueatlas.marker.ebwizardry.library_ruins=Library Ruins
|
||||
|
||||
spell.ebwizardry\:magic_missile_festive=Magic Mistletoe
|
||||
spell.ebwizardry\:summon_snow_golem_festive=Summon Frosty The Snowman
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"pools": [
|
||||
{
|
||||
"name": "wizardry",
|
||||
"conditions": [
|
||||
{
|
||||
"condition": "random_chance",
|
||||
"chance": 0.6
|
||||
}
|
||||
],
|
||||
"rolls": {
|
||||
"min": 0,
|
||||
"max": 2
|
||||
},
|
||||
"entries": [
|
||||
{
|
||||
"type": "loot_table",
|
||||
"name": "ebwizardry:subsets/arcane_tomes",
|
||||
"weight": 2
|
||||
},
|
||||
{
|
||||
"type": "loot_table",
|
||||
"name": "ebwizardry:subsets/wand_upgrades",
|
||||
"weight": 1
|
||||
},
|
||||
{
|
||||
"type": "item",
|
||||
"name": "ebwizardry:spell_book",
|
||||
"weight": 20,
|
||||
"functions": [
|
||||
{
|
||||
"function": "ebwizardry:random_spell",
|
||||
"undiscovered_bias": 0.3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "item",
|
||||
"name": "ebwizardry:scroll",
|
||||
"weight": 4,
|
||||
"functions": [
|
||||
{
|
||||
"function": "ebwizardry:random_spell",
|
||||
"undiscovered_bias": 0.3
|
||||
},
|
||||
{
|
||||
"function": "set_count",
|
||||
"count": {
|
||||
"min": 1,
|
||||
"max": 5
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "item",
|
||||
"name": "ebwizardry:identification_scroll",
|
||||
"weight": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
Reference in New Issue
Block a user