diff --git a/pom.xml b/pom.xml index 1a5ea3402..4776469ba 100644 --- a/pom.xml +++ b/pom.xml @@ -95,7 +95,7 @@ org.polydev gaea - 1.10.46 + 1.10.47 me.lucko diff --git a/src/main/java/com/dfsek/terra/config/ConfigUtil.java b/src/main/java/com/dfsek/terra/config/ConfigUtil.java index d6572765d..39bf7fe96 100644 --- a/src/main/java/com/dfsek/terra/config/ConfigUtil.java +++ b/src/main/java/com/dfsek/terra/config/ConfigUtil.java @@ -9,6 +9,7 @@ import com.dfsek.terra.config.genconfig.CarverConfig; import com.dfsek.terra.config.genconfig.FloraConfig; import com.dfsek.terra.config.genconfig.OreConfig; import com.dfsek.terra.config.genconfig.PaletteConfig; +import com.dfsek.terra.config.genconfig.StructureConfig; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.plugin.java.JavaPlugin; @@ -21,7 +22,7 @@ import java.util.stream.Collectors; public class ConfigUtil { public static boolean debug; - public static long dataSave; + public static long dataSave; // Period of population data saving, in ticks. public static void loadConfig(JavaPlugin main) { main.saveDefaultConfig(); FileConfiguration config = main.getConfig(); @@ -42,6 +43,8 @@ public class ConfigUtil { new ConfigLoader("abstract" + File.separator + "biomes").load(main, AbstractBiomeConfig.class); + new ConfigLoader("structure" + File.separator + "single").load(main, StructureConfig.class); + TerraBiomeGrid.invalidate(); BiomeZone.invalidate(); // Invalidate BiomeZone and BiomeGrid caches to prevent old instances from being accessed. new ConfigLoader("biomes").load(main, BiomeConfig.class); diff --git a/src/main/java/com/dfsek/terra/config/genconfig/BiomeConfigUtil.java b/src/main/java/com/dfsek/terra/config/genconfig/BiomeConfigUtil.java index e48681d46..7522834ab 100644 --- a/src/main/java/com/dfsek/terra/config/genconfig/BiomeConfigUtil.java +++ b/src/main/java/com/dfsek/terra/config/genconfig/BiomeConfigUtil.java @@ -15,7 +15,7 @@ import java.util.Map; import java.util.Random; public class BiomeConfigUtil { - public static Map> getSlabPalettes(List> paletteConfigSection, TerraConfigObject config) throws InvalidConfigurationException { + protected static Map> getSlabPalettes(List> paletteConfigSection, TerraConfigObject config) throws InvalidConfigurationException { Map> paletteMap = new HashMap<>(); for(Map e : paletteConfigSection) { diff --git a/src/main/java/com/dfsek/terra/config/genconfig/StructureConfig.java b/src/main/java/com/dfsek/terra/config/genconfig/StructureConfig.java index 0f1a4f2c1..6c8786dc4 100644 --- a/src/main/java/com/dfsek/terra/config/genconfig/StructureConfig.java +++ b/src/main/java/com/dfsek/terra/config/genconfig/StructureConfig.java @@ -1,23 +1,66 @@ package com.dfsek.terra.config.genconfig; +import com.dfsek.terra.Range; +import com.dfsek.terra.Terra; import com.dfsek.terra.config.TerraConfigObject; +import com.dfsek.terra.population.StructurePopulator; +import com.dfsek.terra.structure.GaeaStructure; +import com.dfsek.terra.structure.StructureSpawn; +import com.dfsek.terra.structure.StructureSpawnRequirement; import org.bukkit.configuration.InvalidConfigurationException; import java.io.File; import java.io.IOException; +import java.util.Objects; public class StructureConfig extends TerraConfigObject { + private GaeaStructure structure; + private StructureSpawn spawn; + private String id; + private Range searchStart; + private Range bound; + StructurePopulator.SearchType type; public StructureConfig(File file) throws IOException, InvalidConfigurationException { super(file); } @Override public void init() throws InvalidConfigurationException { - + try { + structure = GaeaStructure.load(new File(Terra.getInstance().getDataFolder() + File.separator + "config" + File.separator + "structures" + File.separator + "data", Objects.requireNonNull(getString("file")))); + } catch(IOException | NullPointerException e) { + throw new InvalidConfigurationException("Unable to locate structure: " + getString("file")); + } + if(!contains("id")) throw new InvalidConfigurationException("No ID specified!"); + id = getString("id"); + spawn = new StructureSpawn(getInt("spawn.width", 500), getInt("spawn.padding", 100)); + searchStart = new Range(getInt("spawn.start.min", 72), getInt("spawn.start.max", 72)); + bound = new Range(getInt("spawn.bound.min", 48), getInt("spawn.bound.max", 72)); + try { + type = StructurePopulator.SearchType.valueOf(getString("spawn,search", "DOWN")); + } catch(IllegalArgumentException e) { + throw new InvalidConfigurationException("Invalid search type, " + getString("spawn,search")); + } } @Override public String getID() { - return null; + return id; + } + + public GaeaStructure getStructure() { + return structure; + } + + public StructureSpawn getSpawn() { + return spawn; + } + + public Range getBound() { + return bound; + } + + public Range getSearchStart() { + return searchStart; } } diff --git a/src/main/java/com/dfsek/terra/generation/SlabGenerator.java b/src/main/java/com/dfsek/terra/generation/SlabGenerator.java index 2624193f3..8483c6133 100644 --- a/src/main/java/com/dfsek/terra/generation/SlabGenerator.java +++ b/src/main/java/com/dfsek/terra/generation/SlabGenerator.java @@ -73,7 +73,7 @@ public class SlabGenerator extends GenerationPopulator { BlockData slab = slabs.getOrDefault(down.getMaterial(), AIRPALETTE).get(0, block.getBlockX(), block.getBlockZ()); if(slab instanceof Waterlogged) { ((Waterlogged) slab).setWaterlogged(orig.matches(WATER)); - } + } else if(orig.matches(WATER)) return; chunk.setBlock(block.getBlockX(), block.getBlockY(), block.getBlockZ(), slab); } } diff --git a/src/main/java/com/dfsek/terra/generation/TerraChunkGenerator.java b/src/main/java/com/dfsek/terra/generation/TerraChunkGenerator.java index b609fbdee..d67270e35 100644 --- a/src/main/java/com/dfsek/terra/generation/TerraChunkGenerator.java +++ b/src/main/java/com/dfsek/terra/generation/TerraChunkGenerator.java @@ -9,6 +9,7 @@ import com.dfsek.terra.population.FloraPopulator; import com.dfsek.terra.population.OrePopulator; import com.dfsek.terra.population.StructurePopulator; import com.dfsek.terra.population.TreePopulator; +import com.dfsek.terra.structure.StructureSpawnRequirement; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.World; @@ -51,6 +52,7 @@ public class TerraChunkGenerator extends GaeaChunkGenerator { @Override public ChunkData generateBase(@NotNull World world, @NotNull Random random, int chunkX, int chunkZ, FastNoise fastNoise) { if(needsLoad) load(world); + StructureSpawnRequirement.putNoise(world, fastNoise); // Assign noise to world to be used for structures. ChunkData chunk = createChunkData(world); int xOrig = (chunkX << 4); int zOrig = (chunkZ << 4); @@ -130,4 +132,6 @@ public class TerraChunkGenerator extends GaeaChunkGenerator { public boolean shouldGenerateStructures() { return true; } + + } diff --git a/src/main/java/com/dfsek/terra/math/BaseHeightFunction.java b/src/main/java/com/dfsek/terra/math/BaseHeightFunction.java new file mode 100644 index 000000000..a709ef982 --- /dev/null +++ b/src/main/java/com/dfsek/terra/math/BaseHeightFunction.java @@ -0,0 +1,29 @@ +package com.dfsek.terra.math; + +import org.polydev.gaea.math.FastNoise; +import org.polydev.gaea.math.parsii.eval.Expression; +import org.polydev.gaea.math.parsii.eval.Function; + +import java.util.List; + +public class BaseHeightFunction implements Function { + private FastNoise gen; + @Override + public int getNumberOfArguments() { + return 3; + } + + @Override + public double eval(List list) { + return gen.getSimplexFractal((float) list.get(0).evaluate(), (float) list.get(1).evaluate(), (float) list.get(2).evaluate()); + } + + public void setNoise(FastNoise gen) { + this.gen = gen; + } + + @Override + public boolean isNaturalFunction() { + return true; + } +} diff --git a/src/main/java/com/dfsek/terra/population/StructurePopulator.java b/src/main/java/com/dfsek/terra/population/StructurePopulator.java index c28cadf06..8b55d168e 100644 --- a/src/main/java/com/dfsek/terra/population/StructurePopulator.java +++ b/src/main/java/com/dfsek/terra/population/StructurePopulator.java @@ -4,6 +4,7 @@ import com.dfsek.terra.Terra; import com.dfsek.terra.TerraProfiler; import com.dfsek.terra.structure.GaeaStructure; import com.dfsek.terra.structure.StructureSpawn; +import com.dfsek.terra.structure.StructureSpawnRequirement; import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.Location; @@ -35,12 +36,23 @@ public class StructurePopulator extends BlockPopulator { int cx = (chunk.getX() << 4); int cz = (chunk.getZ() << 4); Location spawn = spawnTest.getNearestSpawn(cx+ 8, cz + 8, world.getSeed()).toLocation(world); - spawn.setY(72); - if(Math.abs((cx+8)-spawn.getBlockX()) <= horizontal && Math.abs((cz+8)-spawn.getBlockZ()) <= horizontal) { - try(ProfileFuture ignore = TerraProfiler.fromWorld(world).measure("StructurePasteTime")) { - struc.paste(spawn, chunk, GaeaStructure.Rotation.fromDegrees(random.nextInt(4)*90), Collections.emptyList()); + main: for(int y = 72; y > 0; y--) { + spawn.setY(y); + for(StructureSpawnRequirement s : struc.getSpawns()) { + if(!s.isValidSpawn(spawn)) continue main; + } + Bukkit.getLogger().info("Valid spawn at " + spawn); + if(Math.abs((cx+8)-spawn.getBlockX()) <= horizontal && Math.abs((cz+8)-spawn.getBlockZ()) <= horizontal) { + try(ProfileFuture ignore = TerraProfiler.fromWorld(world).measure("StructurePasteTime")) { + struc.paste(spawn, chunk, GaeaStructure.Rotation.fromDegrees(new Random(spawn.hashCode()).nextInt(4)*90), Collections.emptyList()); + break; + } } } + } } + public enum SearchType { + UP, DOWN + } } diff --git a/src/main/java/com/dfsek/terra/structure/GaeaStructure.java b/src/main/java/com/dfsek/terra/structure/GaeaStructure.java index d14a59c1a..eaf0de8c0 100644 --- a/src/main/java/com/dfsek/terra/structure/GaeaStructure.java +++ b/src/main/java/com/dfsek/terra/structure/GaeaStructure.java @@ -24,6 +24,7 @@ import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -33,6 +34,7 @@ import java.util.function.Consumer; public class GaeaStructure implements Serializable { public static final long serialVersionUID = -6664585217063842035L; private final StructureContainedBlock[][][] structure; + private final ArrayList spawns = new ArrayList<>(); private final GaeaStructureInfo structureInfo; private final String id; private final UUID uuid; @@ -68,6 +70,21 @@ public class GaeaStructure implements Serializable { if(s.getLine(1).equals("[CENTER]")) { centerX = x; centerZ = z; + } else if(s.getLine(1).startsWith("[SPAWN=") && s.getLine(1).endsWith("]")) { + String og = s.getLine(1); + String spawn = og.substring(og.indexOf("=")+1, og.length()-1); + switch(spawn.toUpperCase()) { + case "LAND": + spawns.add(new StructureSpawnRequirement(StructureSpawnRequirement.RequirementType.LAND, x, y, z)); + break; + case "OCEAN": + spawns.add(new StructureSpawnRequirement(StructureSpawnRequirement.RequirementType.OCEAN, x, y, z)); + break; + case "AIR": + spawns.add(new StructureSpawnRequirement(StructureSpawnRequirement.RequirementType.AIR, x, y, z)); + break; + default: throw new InitializationException("Invalid spawn type, " + spawn); + } } } catch(IllegalArgumentException e) { throw new InitializationException("Invalid Block Data on sign: \"" + s.getLine(2) + s.getLine(3) + "\""); @@ -82,15 +99,36 @@ public class GaeaStructure implements Serializable { structureInfo = new GaeaStructureInfo(l2.getBlockX()-l1.getBlockX()+1, l2.getBlockY()-l1.getBlockY()+1, l2.getBlockZ()-l1.getBlockZ()+1, centerX, centerZ); } + public ArrayList getSpawns() { + return spawns; + } + + /** + * Get GaeaStructureInfo object + * @return Structure Info + */ @NotNull public GaeaStructureInfo getStructureInfo() { return structureInfo; } + /** + * Paste the structure at a Location, ignoring chunk boundaries. + * @param origin Origin location + * @param r Rotation + * @param m Mirror + */ public void paste(@NotNull Location origin, Rotation r, List m) { this.executeForBlocksInRange(getRange(Axis.X), getRange(Axis.Y), getRange(Axis.Z), block -> pasteBlock(block, origin, r, m), r, m); } + /** + * Rotate and mirror a coordinate pair. + * @param arr Array containing X and Z coordinates. + * @param r Rotation + * @param m Mirror + * @return Rotated coordinate pair + */ private int[] getRotatedCoords(int[] arr, Rotation r, List m) { int[] cp = Arrays.copyOf(arr, 2); switch(r) { @@ -112,34 +150,6 @@ public class GaeaStructure implements Serializable { return arr; } - /** - * Inverse of {@link GaeaStructure#getRotatedCoords(int[], Rotation, List)}, gets the coordinates before rotation - * @param arr 2-integer array holding X and Z coordinates - * @param r Rotation - * @param m Mirror - * @return New coordinates - */ - private int[] getOriginalCoords(int[] arr, Rotation r, List m) { - int[] cp = Arrays.copyOf(arr, 2); - switch(r) { - case CW_90: - arr[1] = cp[0]; - arr[0] = - cp[1]; - break; - case CCW_90: - arr[1] = - cp[0]; - arr[0] = cp[1]; - break; - case CW_180: - arr[1] = - cp[1]; - arr[0] = - cp[0]; - break; - } - if(m.contains(Mirror.X)) arr[0] = - arr[0]; - if(m.contains(Mirror.Z)) arr[1] = - arr[1]; - return arr; - } - /** * Get the BlockFace with rotation and mirrors applied to it * @param f BlockFace to apply rotation to @@ -246,11 +256,9 @@ public class GaeaStructure implements Serializable { if(!data.getMaterial().equals(Material.STRUCTURE_VOID)) { if(data instanceof Rotatable) { BlockFace rt = getRotatedFace(((Rotatable) data).getRotation(), r, m); - Bukkit.getLogger().info("Previous: " + ((Rotatable) data).getRotation() + ", New: " + rt); ((Rotatable) data).setRotation(rt); } else if(data instanceof Directional) { BlockFace rt = getRotatedFace(((Directional) data).getFacing(), r, m); - Bukkit.getLogger().info("Previous: " + ((Directional) data).getFacing() + ", New: " + rt); ((Directional) data).setFacing(rt); } worldBlock.setBlockData(data, false); @@ -276,7 +284,6 @@ public class GaeaStructure implements Serializable { int[] c = getRotatedCoords(new int[] {x-structureInfo.getCenterX(), z-structureInfo.getCenterZ()}, r, m); c[0] = c[0] + structureInfo.getCenterX(); c[1] = c[1] + structureInfo.getCenterZ(); - Bukkit.getLogger().info("Before: " + x + ", " + z + " After: " + c[0] + ", " + c[1]); if(isInStructure(c[0], y, c[1])) { StructureContainedBlock b = structure[c[0]][c[1]][y]; exec.accept(new StructureContainedBlock(x - getStructureInfo().getCenterX(), y, z - getStructureInfo().getCenterZ(), b.getState(), b.getBlockData())); @@ -297,6 +304,15 @@ public class GaeaStructure implements Serializable { return x < structureInfo.getSizeX() && y < structureInfo.getSizeY() && z < structureInfo.getSizeZ() && x >= 0 && y >= 0 && z >= 0; } + /** + * From an origin location (First bound) fetch the second bound. + * @param origin Origin location + * @return Other bound location + */ + public Location getOtherBound(Location origin) { + return origin.clone().add(structureInfo.getSizeX(), structureInfo.getSizeY(), structureInfo.getSizeZ()); + } + /** * Save the structure to a file * @param f File to save to diff --git a/src/main/java/com/dfsek/terra/structure/StructureSpawnRequirement.java b/src/main/java/com/dfsek/terra/structure/StructureSpawnRequirement.java new file mode 100644 index 000000000..a0f678e90 --- /dev/null +++ b/src/main/java/com/dfsek/terra/structure/StructureSpawnRequirement.java @@ -0,0 +1,79 @@ +package com.dfsek.terra.structure; + +import com.dfsek.terra.biome.TerraBiomeGrid; +import com.dfsek.terra.biome.UserDefinedBiome; +import com.dfsek.terra.config.genconfig.BiomeConfig; +import org.bukkit.Location; +import org.bukkit.World; +import org.polydev.gaea.biome.Generator; +import org.polydev.gaea.generation.GenerationPhase; +import org.polydev.gaea.math.FastNoise; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +public class StructureSpawnRequirement implements Serializable { + private static final long serialVersionUID = -175639605885943679L; + private static final transient Map noiseMap = new HashMap<>(); + private final RequirementType type; + private final int x, y, z; + + public StructureSpawnRequirement(RequirementType type, int x, int y, int z) { + this.type = type; + this.x = x; + this.y = y; + this.z = z; + } + + public RequirementType getType() { + return type; + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public int getZ() { + return z; + } + + public boolean isValidSpawn(Location origin) { + return type.matches(origin.getWorld(), origin.getBlockX()+x, origin.getBlockY()+y, origin.getBlockZ()+z); + } + + public enum RequirementType { + AIR { + @Override + public boolean matches(World w, int x, int y, int z) { + UserDefinedBiome b = (UserDefinedBiome) TerraBiomeGrid.fromWorld(w).getBiome(x, z, GenerationPhase.POPULATE); + if(y <= BiomeConfig.fromBiome(b).getSeaLevel()) return false; + return b.getGenerator().getNoise(getNoise(w), w, x, y, z) <= 0; + } + }, OCEAN { + @Override + public boolean matches(World w, int x, int y, int z) { + UserDefinedBiome b = (UserDefinedBiome) TerraBiomeGrid.fromWorld(w).getBiome(x, z, GenerationPhase.POPULATE); + if(y > BiomeConfig.fromBiome(b).getSeaLevel()) return false; + return b.getGenerator().getNoise(getNoise(w), w, x, y, z) <= 0; + } + }, LAND { + @Override + public boolean matches(World w, int x, int y, int z) { + UserDefinedBiome b = (UserDefinedBiome) TerraBiomeGrid.fromWorld(w).getBiome(x, z, GenerationPhase.POPULATE); + return b.getGenerator().getNoise(getNoise(w), w, x, y, z) > 0; + } + }; + public abstract boolean matches(World w, int x, int y, int z); + } + public static void putNoise(World w, FastNoise noise) { + noiseMap.putIfAbsent(w, noise); + } + private static FastNoise getNoise(World w) { + return noiseMap.get(w); + } +}