Implement basic structure config

This commit is contained in:
dfsek 2020-09-29 01:38:17 -07:00
parent 89224723e6
commit eb208ed9fe
10 changed files with 227 additions and 41 deletions

View File

@ -95,7 +95,7 @@
<dependency> <dependency>
<groupId>org.polydev</groupId> <groupId>org.polydev</groupId>
<artifactId>gaea</artifactId> <artifactId>gaea</artifactId>
<version>1.10.46</version> <version>1.10.47</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>me.lucko</groupId> <groupId>me.lucko</groupId>

View File

@ -9,6 +9,7 @@ import com.dfsek.terra.config.genconfig.CarverConfig;
import com.dfsek.terra.config.genconfig.FloraConfig; import com.dfsek.terra.config.genconfig.FloraConfig;
import com.dfsek.terra.config.genconfig.OreConfig; import com.dfsek.terra.config.genconfig.OreConfig;
import com.dfsek.terra.config.genconfig.PaletteConfig; import com.dfsek.terra.config.genconfig.PaletteConfig;
import com.dfsek.terra.config.genconfig.StructureConfig;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
@ -21,7 +22,7 @@ import java.util.stream.Collectors;
public class ConfigUtil { public class ConfigUtil {
public static boolean debug; 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) { public static void loadConfig(JavaPlugin main) {
main.saveDefaultConfig(); main.saveDefaultConfig();
FileConfiguration config = main.getConfig(); FileConfiguration config = main.getConfig();
@ -42,6 +43,8 @@ public class ConfigUtil {
new ConfigLoader("abstract" + File.separator + "biomes").load(main, AbstractBiomeConfig.class); new ConfigLoader("abstract" + File.separator + "biomes").load(main, AbstractBiomeConfig.class);
new ConfigLoader("structure" + File.separator + "single").load(main, StructureConfig.class);
TerraBiomeGrid.invalidate(); TerraBiomeGrid.invalidate();
BiomeZone.invalidate(); // Invalidate BiomeZone and BiomeGrid caches to prevent old instances from being accessed. BiomeZone.invalidate(); // Invalidate BiomeZone and BiomeGrid caches to prevent old instances from being accessed.
new ConfigLoader("biomes").load(main, BiomeConfig.class); new ConfigLoader("biomes").load(main, BiomeConfig.class);

View File

@ -15,7 +15,7 @@ import java.util.Map;
import java.util.Random; import java.util.Random;
public class BiomeConfigUtil { public class BiomeConfigUtil {
public static Map<Material, Palette<BlockData>> getSlabPalettes(List<Map<?, ?>> paletteConfigSection, TerraConfigObject config) throws InvalidConfigurationException { protected static Map<Material, Palette<BlockData>> getSlabPalettes(List<Map<?, ?>> paletteConfigSection, TerraConfigObject config) throws InvalidConfigurationException {
Map<Material, Palette<BlockData>> paletteMap = new HashMap<>(); Map<Material, Palette<BlockData>> paletteMap = new HashMap<>();
for(Map<?, ?> e : paletteConfigSection) { for(Map<?, ?> e : paletteConfigSection) {

View File

@ -1,23 +1,66 @@
package com.dfsek.terra.config.genconfig; 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.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 org.bukkit.configuration.InvalidConfigurationException;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Objects;
public class StructureConfig extends TerraConfigObject { 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 { public StructureConfig(File file) throws IOException, InvalidConfigurationException {
super(file); super(file);
} }
@Override @Override
public void init() throws InvalidConfigurationException { 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 @Override
public String getID() { 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;
} }
} }

View File

@ -73,7 +73,7 @@ public class SlabGenerator extends GenerationPopulator {
BlockData slab = slabs.getOrDefault(down.getMaterial(), AIRPALETTE).get(0, block.getBlockX(), block.getBlockZ()); BlockData slab = slabs.getOrDefault(down.getMaterial(), AIRPALETTE).get(0, block.getBlockX(), block.getBlockZ());
if(slab instanceof Waterlogged) { if(slab instanceof Waterlogged) {
((Waterlogged) slab).setWaterlogged(orig.matches(WATER)); ((Waterlogged) slab).setWaterlogged(orig.matches(WATER));
} } else if(orig.matches(WATER)) return;
chunk.setBlock(block.getBlockX(), block.getBlockY(), block.getBlockZ(), slab); chunk.setBlock(block.getBlockX(), block.getBlockY(), block.getBlockZ(), slab);
} }
} }

View File

@ -9,6 +9,7 @@ import com.dfsek.terra.population.FloraPopulator;
import com.dfsek.terra.population.OrePopulator; import com.dfsek.terra.population.OrePopulator;
import com.dfsek.terra.population.StructurePopulator; import com.dfsek.terra.population.StructurePopulator;
import com.dfsek.terra.population.TreePopulator; import com.dfsek.terra.population.TreePopulator;
import com.dfsek.terra.structure.StructureSpawnRequirement;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.World; import org.bukkit.World;
@ -51,6 +52,7 @@ public class TerraChunkGenerator extends GaeaChunkGenerator {
@Override @Override
public ChunkData generateBase(@NotNull World world, @NotNull Random random, int chunkX, int chunkZ, FastNoise fastNoise) { public ChunkData generateBase(@NotNull World world, @NotNull Random random, int chunkX, int chunkZ, FastNoise fastNoise) {
if(needsLoad) load(world); if(needsLoad) load(world);
StructureSpawnRequirement.putNoise(world, fastNoise); // Assign noise to world to be used for structures.
ChunkData chunk = createChunkData(world); ChunkData chunk = createChunkData(world);
int xOrig = (chunkX << 4); int xOrig = (chunkX << 4);
int zOrig = (chunkZ << 4); int zOrig = (chunkZ << 4);
@ -130,4 +132,6 @@ public class TerraChunkGenerator extends GaeaChunkGenerator {
public boolean shouldGenerateStructures() { public boolean shouldGenerateStructures() {
return true; return true;
} }
} }

View File

@ -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<Expression> 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;
}
}

View File

@ -4,6 +4,7 @@ import com.dfsek.terra.Terra;
import com.dfsek.terra.TerraProfiler; import com.dfsek.terra.TerraProfiler;
import com.dfsek.terra.structure.GaeaStructure; import com.dfsek.terra.structure.GaeaStructure;
import com.dfsek.terra.structure.StructureSpawn; import com.dfsek.terra.structure.StructureSpawn;
import com.dfsek.terra.structure.StructureSpawnRequirement;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Chunk; import org.bukkit.Chunk;
import org.bukkit.Location; import org.bukkit.Location;
@ -35,12 +36,23 @@ public class StructurePopulator extends BlockPopulator {
int cx = (chunk.getX() << 4); int cx = (chunk.getX() << 4);
int cz = (chunk.getZ() << 4); int cz = (chunk.getZ() << 4);
Location spawn = spawnTest.getNearestSpawn(cx+ 8, cz + 8, world.getSeed()).toLocation(world); Location spawn = spawnTest.getNearestSpawn(cx+ 8, cz + 8, world.getSeed()).toLocation(world);
spawn.setY(72); main: for(int y = 72; y > 0; y--) {
if(Math.abs((cx+8)-spawn.getBlockX()) <= horizontal && Math.abs((cz+8)-spawn.getBlockZ()) <= horizontal) { spawn.setY(y);
try(ProfileFuture ignore = TerraProfiler.fromWorld(world).measure("StructurePasteTime")) { for(StructureSpawnRequirement s : struc.getSpawns()) {
struc.paste(spawn, chunk, GaeaStructure.Rotation.fromDegrees(random.nextInt(4)*90), Collections.emptyList()); 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
}
} }

View File

@ -24,6 +24,7 @@ import java.io.InputStream;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; import java.io.ObjectOutputStream;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -33,6 +34,7 @@ import java.util.function.Consumer;
public class GaeaStructure implements Serializable { public class GaeaStructure implements Serializable {
public static final long serialVersionUID = -6664585217063842035L; public static final long serialVersionUID = -6664585217063842035L;
private final StructureContainedBlock[][][] structure; private final StructureContainedBlock[][][] structure;
private final ArrayList<StructureSpawnRequirement> spawns = new ArrayList<>();
private final GaeaStructureInfo structureInfo; private final GaeaStructureInfo structureInfo;
private final String id; private final String id;
private final UUID uuid; private final UUID uuid;
@ -68,6 +70,21 @@ public class GaeaStructure implements Serializable {
if(s.getLine(1).equals("[CENTER]")) { if(s.getLine(1).equals("[CENTER]")) {
centerX = x; centerX = x;
centerZ = z; 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) { } catch(IllegalArgumentException e) {
throw new InitializationException("Invalid Block Data on sign: \"" + s.getLine(2) + s.getLine(3) + "\""); 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); structureInfo = new GaeaStructureInfo(l2.getBlockX()-l1.getBlockX()+1, l2.getBlockY()-l1.getBlockY()+1, l2.getBlockZ()-l1.getBlockZ()+1, centerX, centerZ);
} }
public ArrayList<StructureSpawnRequirement> getSpawns() {
return spawns;
}
/**
* Get GaeaStructureInfo object
* @return Structure Info
*/
@NotNull @NotNull
public GaeaStructureInfo getStructureInfo() { public GaeaStructureInfo getStructureInfo() {
return structureInfo; 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<Mirror> m) { public void paste(@NotNull Location origin, Rotation r, List<Mirror> m) {
this.executeForBlocksInRange(getRange(Axis.X), getRange(Axis.Y), getRange(Axis.Z), block -> pasteBlock(block, origin, r, m), r, 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<Mirror> m) { private int[] getRotatedCoords(int[] arr, Rotation r, List<Mirror> m) {
int[] cp = Arrays.copyOf(arr, 2); int[] cp = Arrays.copyOf(arr, 2);
switch(r) { switch(r) {
@ -112,34 +150,6 @@ public class GaeaStructure implements Serializable {
return arr; return arr;
} }
/**
* Inverse of {@link GaeaStructure#getRotatedCoords(int[], Rotation, List)}, gets the coordinates <i>before</i> 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<Mirror> 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 * Get the BlockFace with rotation and mirrors applied to it
* @param f BlockFace to apply rotation to * @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.getMaterial().equals(Material.STRUCTURE_VOID)) {
if(data instanceof Rotatable) { if(data instanceof Rotatable) {
BlockFace rt = getRotatedFace(((Rotatable) data).getRotation(), r, m); BlockFace rt = getRotatedFace(((Rotatable) data).getRotation(), r, m);
Bukkit.getLogger().info("Previous: " + ((Rotatable) data).getRotation() + ", New: " + rt);
((Rotatable) data).setRotation(rt); ((Rotatable) data).setRotation(rt);
} else if(data instanceof Directional) { } else if(data instanceof Directional) {
BlockFace rt = getRotatedFace(((Directional) data).getFacing(), r, m); BlockFace rt = getRotatedFace(((Directional) data).getFacing(), r, m);
Bukkit.getLogger().info("Previous: " + ((Directional) data).getFacing() + ", New: " + rt);
((Directional) data).setFacing(rt); ((Directional) data).setFacing(rt);
} }
worldBlock.setBlockData(data, false); 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); int[] c = getRotatedCoords(new int[] {x-structureInfo.getCenterX(), z-structureInfo.getCenterZ()}, r, m);
c[0] = c[0] + structureInfo.getCenterX(); c[0] = c[0] + structureInfo.getCenterX();
c[1] = c[1] + structureInfo.getCenterZ(); c[1] = c[1] + structureInfo.getCenterZ();
Bukkit.getLogger().info("Before: " + x + ", " + z + " After: " + c[0] + ", " + c[1]);
if(isInStructure(c[0], y, c[1])) { if(isInStructure(c[0], y, c[1])) {
StructureContainedBlock b = structure[c[0]][c[1]][y]; StructureContainedBlock b = structure[c[0]][c[1]][y];
exec.accept(new StructureContainedBlock(x - getStructureInfo().getCenterX(), y, z - getStructureInfo().getCenterZ(), b.getState(), b.getBlockData())); 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; 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 * Save the structure to a file
* @param f File to save to * @param f File to save to

View File

@ -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<World, FastNoise> 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);
}
}