diff --git a/build.gradle.kts b/build.gradle.kts index f0b092ba9..aa96089b6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,6 @@ import com.dfsek.terra.getGitHash -val versionObj = Version("3", "2", "0", true) +val versionObj = Version("4", "0", "0", true) allprojects { version = versionObj diff --git a/common/src/main/java/com/dfsek/terra/api/world/carving/Carver.java b/common/src/main/java/com/dfsek/terra/api/world/carving/Carver.java new file mode 100644 index 000000000..c0b21ae26 --- /dev/null +++ b/common/src/main/java/com/dfsek/terra/api/world/carving/Carver.java @@ -0,0 +1,38 @@ +package com.dfsek.terra.api.world.carving; + +import com.dfsek.terra.api.math.vector.Vector3; +import com.dfsek.terra.api.platform.world.World; +import net.jafama.FastMath; + +import java.util.Random; +import java.util.function.BiConsumer; + +public abstract class Carver { + private final int minY; + private final int maxY; + private final double sixtyFourSq = FastMath.pow(64, 2); + private int carvingRadius = 4; + + public Carver(int minY, int maxY) { + this.minY = minY; + this.maxY = maxY; + } + + public abstract void carve(int chunkX, int chunkZ, World w, BiConsumer consumer); + + public int getCarvingRadius() { + return carvingRadius; + } + + public void setCarvingRadius(int carvingRadius) { + this.carvingRadius = carvingRadius; + } + + public abstract Worm getWorm(long seed, Vector3 l); + + public abstract boolean isChunkCarved(World w, int chunkX, int chunkZ, Random r); + + public enum CarvingType { + CENTER, WALL, TOP, BOTTOM + } +} diff --git a/common/src/main/java/com/dfsek/terra/api/world/carving/Worm.java b/common/src/main/java/com/dfsek/terra/api/world/carving/Worm.java new file mode 100644 index 000000000..71e2b0de5 --- /dev/null +++ b/common/src/main/java/com/dfsek/terra/api/world/carving/Worm.java @@ -0,0 +1,119 @@ +package com.dfsek.terra.api.world.carving; + +import com.dfsek.terra.api.math.vector.Vector3; +import net.jafama.FastMath; + +import java.util.Random; +import java.util.function.BiConsumer; + +public abstract class Worm { + private final Random r; + private final Vector3 origin; + private final Vector3 running; + private final int length; + private int topCut = 0; + private int bottomCut = 0; + private int[] radius = new int[] {0, 0, 0}; + + public Worm(int length, Random r, Vector3 origin) { + this.r = r; + this.length = length; + this.origin = origin; + this.running = origin; + } + + public void setBottomCut(int bottomCut) { + this.bottomCut = bottomCut; + } + + public void setTopCut(int topCut) { + this.topCut = topCut; + } + + public Vector3 getOrigin() { + return origin; + } + + public int getLength() { + return length; + } + + public Vector3 getRunning() { + return running; + } + + public WormPoint getPoint() { + return new WormPoint(running, radius, topCut, bottomCut); + } + + public int[] getRadius() { + return radius; + } + + public void setRadius(int[] radius) { + this.radius = radius; + } + + public Random getRandom() { + return r; + } + + public abstract void step(); + + public static class WormPoint { + private final Vector3 origin; + private final int topCut; + private final int bottomCut; + private final int[] rad; + + public WormPoint(Vector3 origin, int[] rad, int topCut, int bottomCut) { + this.origin = origin; + this.rad = rad; + this.topCut = topCut; + this.bottomCut = bottomCut; + } + + private static double ellipseEquation(int x, int y, int z, double xr, double yr, double zr) { + return (FastMath.pow2(x) / FastMath.pow2(xr + 0.5D)) + (FastMath.pow2(y) / FastMath.pow2(yr + 0.5D)) + (FastMath.pow2(z) / FastMath.pow2(zr + 0.5D)); + } + + public Vector3 getOrigin() { + return origin; + } + + public int getRadius(int index) { + return rad[index]; + } + + public void carve(int chunkX, int chunkZ, BiConsumer consumer) { + int xRad = getRadius(0); + int yRad = getRadius(1); + int zRad = getRadius(2); + int originX = (chunkX << 4); + int originZ = (chunkZ << 4); + for(int x = -xRad - 1; x <= xRad + 1; x++) { + if(!(FastMath.floorDiv(origin.getBlockX() + x, 16) == chunkX)) continue; + for(int z = -zRad - 1; z <= zRad + 1; z++) { + if(!(FastMath.floorDiv(origin.getBlockZ() + z, 16) == chunkZ)) continue; + for(int y = -yRad - 1; y <= yRad + 1; y++) { + Vector3 position = origin.clone().add(new Vector3(x, y, z)); + if(position.getY() < 0 || position.getY() > 255) continue; + double eq = ellipseEquation(x, y, z, xRad, yRad, zRad); + if(eq <= 1 && + y >= -yRad - 1 + bottomCut && y <= yRad + 1 - topCut) { + consumer.accept(new Vector3(position.getBlockX() - originX, position.getBlockY(), position.getBlockZ() - originZ), Carver.CarvingType.CENTER); + } else if(eq <= 1.5) { + Carver.CarvingType type = Carver.CarvingType.WALL; + if(y <= -yRad - 1 + bottomCut) { + type = Carver.CarvingType.BOTTOM; + } else if(y >= yRad + 1 - topCut) { + type = Carver.CarvingType.TOP; + } + consumer.accept(new Vector3(position.getBlockX() - originX, position.getBlockY(), position.getBlockZ() - originZ), type); + } + } + } + } + } + } +} diff --git a/common/src/main/java/com/dfsek/terra/carving/CarverCache.java b/common/src/main/java/com/dfsek/terra/carving/CarverCache.java new file mode 100644 index 000000000..d175ff4f2 --- /dev/null +++ b/common/src/main/java/com/dfsek/terra/carving/CarverCache.java @@ -0,0 +1,59 @@ +package com.dfsek.terra.carving; + +import com.dfsek.terra.api.core.TerraPlugin; +import com.dfsek.terra.api.math.MathUtil; +import com.dfsek.terra.api.math.vector.Vector3; +import com.dfsek.terra.api.platform.world.World; +import com.dfsek.terra.api.util.FastRandom; +import com.dfsek.terra.api.util.GlueList; +import com.dfsek.terra.api.world.carving.Worm; +import com.dfsek.terra.biome.TerraBiome; +import com.dfsek.terra.biome.UserDefinedBiome; +import com.dfsek.terra.biome.provider.BiomeProvider; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Random; + +public class CarverCache { + + private final LoadingCache> cache; + private final UserDefinedCarver carver; + + public CarverCache(World w, TerraPlugin main, UserDefinedCarver carver) { + this.carver = carver; + cache = CacheBuilder.newBuilder().maximumSize(main.getTerraConfig().getCarverCacheSize()) + .build(new CacheLoader>() { + @Override + public List load(@NotNull Long key) { + int chunkX = (int) (key >> 32); + int chunkZ = (int) key.longValue(); + BiomeProvider provider = main.getWorld(w).getBiomeProvider(); + if(CarverCache.this.carver.isChunkCarved(w, chunkX, chunkZ, new FastRandom(MathUtil.getCarverChunkSeed(chunkX, chunkZ, w.getSeed() + CarverCache.this.carver.hashCode())))) { + long seed = MathUtil.getCarverChunkSeed(chunkX, chunkZ, w.getSeed()); + CarverCache.this.carver.getSeedVar().setValue(seed); + Random r = new FastRandom(seed); + Worm carving = CarverCache.this.carver.getWorm(seed, new Vector3((chunkX << 4) + r.nextInt(16), CarverCache.this.carver.getConfig().getHeight().get(r), (chunkZ << 4) + r.nextInt(16))); + List points = new GlueList<>(); + for(int i = 0; i < carving.getLength(); i++) { + carving.step(); + TerraBiome biome = provider.getBiome(carving.getRunning().toLocation(w)); + if(!((UserDefinedBiome) biome).getConfig().getCarvers().containsKey(CarverCache.this.carver)) { // Stop if we enter a biome this carver is not present in + return new GlueList<>(); + } + points.add(carving.getPoint()); + } + return points; + } + return new GlueList<>(); + } + }); + } + + public List getPoints(int chunkX, int chunkZ) { + return cache.getUnchecked(MathUtil.squash(chunkX, chunkZ)); + } +} diff --git a/common/src/main/java/com/dfsek/terra/carving/CarverPalette.java b/common/src/main/java/com/dfsek/terra/carving/CarverPalette.java new file mode 100644 index 000000000..249c36957 --- /dev/null +++ b/common/src/main/java/com/dfsek/terra/carving/CarverPalette.java @@ -0,0 +1,54 @@ +package com.dfsek.terra.carving; + +import com.dfsek.terra.api.math.ProbabilityCollection; +import com.dfsek.terra.api.platform.block.BlockData; +import com.dfsek.terra.api.platform.block.MaterialData; +import com.dfsek.terra.util.MaterialSet; + +import java.util.Map; +import java.util.TreeMap; + +@SuppressWarnings({"unchecked", "rawtypes", "RedundantSuppression"}) +public class CarverPalette { + private final boolean blacklist; + private final MaterialSet replace; + private final TreeMap> map = new TreeMap<>(); + private ProbabilityCollection[] layers; + + public CarverPalette(MaterialSet replaceable, boolean blacklist) { + this.blacklist = blacklist; + this.replace = replaceable; + } + + public CarverPalette add(ProbabilityCollection collection, int y) { + map.put(y, collection); + return this; + } + + public ProbabilityCollection get(int y) { + return layers[y]; + } + + public boolean canReplace(MaterialData material) { + return blacklist != replace.contains(material); + } + + /** + * Build the palette to an array. + */ + public void build() { + int size = map.lastKey() + 1; + layers = new ProbabilityCollection[size]; + for(int y = 0; y < size; y++) { + ProbabilityCollection d = null; + for(Map.Entry> e : map.entrySet()) { + if(e.getKey() >= y) { + d = e.getValue(); + break; + } + } + if(d == null) throw new IllegalArgumentException("Null collection at Y=" + y); + layers[y] = d; + } + } +} diff --git a/common/src/main/java/com/dfsek/terra/carving/UserDefinedCarver.java b/common/src/main/java/com/dfsek/terra/carving/UserDefinedCarver.java new file mode 100644 index 000000000..5b550ec1d --- /dev/null +++ b/common/src/main/java/com/dfsek/terra/carving/UserDefinedCarver.java @@ -0,0 +1,213 @@ +package com.dfsek.terra.carving; + +import com.dfsek.terra.api.core.TerraPlugin; +import com.dfsek.terra.api.math.Range; +import com.dfsek.terra.api.math.parsii.defined.UserDefinedFunction; +import com.dfsek.terra.api.math.parsii.noise.NoiseFunction2; +import com.dfsek.terra.api.math.parsii.noise.NoiseFunction3; +import com.dfsek.terra.api.math.vector.Vector3; +import com.dfsek.terra.api.platform.world.World; +import com.dfsek.terra.api.util.FastRandom; +import com.dfsek.terra.api.util.seeded.NoiseSeeded; +import com.dfsek.terra.api.world.carving.Carver; +import com.dfsek.terra.api.world.carving.Worm; +import com.dfsek.terra.biome.UserDefinedBiome; +import com.dfsek.terra.config.loaders.config.function.FunctionTemplate; +import com.dfsek.terra.config.templates.BiomeTemplate; +import com.dfsek.terra.config.templates.CarverTemplate; +import net.jafama.FastMath; +import parsii.eval.Expression; +import parsii.eval.Parser; +import parsii.eval.Scope; +import parsii.eval.Variable; +import parsii.tokenizer.ParseException; + +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; + +public class UserDefinedCarver extends Carver { + private final double[] start; // 0, 1, 2 = x, y, z. + private final double[] mutate; // 0, 1, 2 = x, y, z. 3 = radius. + private final Range length; + private final long hash; + private final int topCut; + private final int bottomCut; + private final CarverTemplate config; + private final Expression xRad; + private final Expression yRad; + private final Expression zRad; + private final Variable lengthVar; + private final Variable position; + private final Variable seedVar; + + private final Variable xOrigin; + private final Variable yOrigin; + private final Variable zOrigin; + + private final Map cacheMap = new ConcurrentHashMap<>(); + private final TerraPlugin main; + private double step = 2; + private Range recalc = new Range(8, 10); + private double recalcMagnitude = 3; + + public UserDefinedCarver(Range height, Range length, double[] start, double[] mutate, List radii, Scope parent, long hash, int topCut, int bottomCut, CarverTemplate config, TerraPlugin main, Map functions, Map definedFunctions) throws ParseException { + super(height.getMin(), height.getMax()); + this.length = length; + this.start = start; + this.mutate = mutate; + this.hash = hash; + this.topCut = topCut; + this.bottomCut = bottomCut; + this.config = config; + this.main = main; + + Parser p = new Parser(); + + functions.forEach((id, noise) -> { + switch(noise.getDimensions()) { + case 2: + p.registerFunction(id, new NoiseFunction2(hash, noise)); + break; + case 3: + p.registerFunction(id, new NoiseFunction3(hash, noise)); + break; + } + }); + + for(Map.Entry entry : definedFunctions.entrySet()) { + String id = entry.getKey(); + FunctionTemplate fun = entry.getValue(); + + Scope functionScope = new Scope().withParent(parent); + List variables = fun.getArgs().stream().map(functionScope::create).collect(Collectors.toList()); + + p.registerFunction(id, new UserDefinedFunction(p.parse(fun.getFunction(), functionScope), variables)); + } + + Scope s = new Scope().withParent(parent); + + lengthVar = s.create("length"); + position = s.create("position"); + seedVar = s.create("seed"); + + xOrigin = s.create("x"); + yOrigin = s.create("y"); + zOrigin = s.create("z"); + + + xRad = p.parse(radii.get(0), s); + yRad = p.parse(radii.get(1), s); + zRad = p.parse(radii.get(2), s); + + } + + @Override + public Worm getWorm(long l, Vector3 vector) { + Random r = new FastRandom(l + hash); + return new UserDefinedWorm(length.get(r) / 2, r, vector, topCut, bottomCut); + } + + protected Variable getSeedVar() { + return seedVar; + } + + protected Variable getLengthVar() { + return lengthVar; + } + + protected Variable getPosition() { + return position; + } + + public void setStep(double step) { + this.step = step; + } + + public void setRecalc(Range recalc) { + this.recalc = recalc; + } + + @Override + public void carve(int chunkX, int chunkZ, World w, BiConsumer consumer) { + synchronized(cacheMap) { + CarverCache cache = cacheMap.computeIfAbsent(w, world -> new CarverCache(world, main, this)); + int carvingRadius = getCarvingRadius(); + for(int x = chunkX - carvingRadius; x <= chunkX + carvingRadius; x++) { + for(int z = chunkZ - carvingRadius; z <= chunkZ + carvingRadius; z++) { + cache.getPoints(x, z).forEach(point -> { + Vector3 origin = point.getOrigin(); + if(FastMath.floorDiv(origin.getBlockX(), 16) != chunkX && FastMath.floorDiv(origin.getBlockZ(), 16) != chunkZ) // We only want to carve this chunk. + return; + point.carve(chunkX, chunkZ, consumer); + }); + } + } + } + } + + public void setRecalcMagnitude(double recalcMagnitude) { + this.recalcMagnitude = recalcMagnitude; + } + + @Override + public boolean isChunkCarved(World w, int chunkX, int chunkZ, Random random) { + BiomeTemplate conf = ((UserDefinedBiome) main.getWorld(w).getBiomeProvider().getBiome((chunkX << 4) + 8, (chunkZ << 4) + 8)).getConfig(); + if(conf.getCarvers().get(this) != null) { + return new FastRandom(random.nextLong() + hash).nextInt(100) < conf.getCarvers().get(this); + } + return false; + } + + public CarverTemplate getConfig() { + return config; + } + + private class UserDefinedWorm extends Worm { + private final Vector3 direction; + private int steps; + private int nextDirection = 0; + private double[] currentRotation = new double[3]; + + public UserDefinedWorm(int length, Random r, Vector3 origin, int topCut, int bottomCut) { + super(length, r, origin); + super.setTopCut(topCut); + super.setBottomCut(bottomCut); + direction = new Vector3((r.nextDouble() - 0.5D) * start[0], (r.nextDouble() - 0.5D) * start[1], (r.nextDouble() - 0.5D) * start[2]).normalize().multiply(step); + position.setValue(0); + lengthVar.setValue(length); + xOrigin.setValue(origin.getX()); + yOrigin.setValue(origin.getY()); + zOrigin.setValue(origin.getZ()); + setRadius(new int[] {(int) (xRad.evaluate()), (int) (yRad.evaluate()), (int) (zRad.evaluate())}); + } + + @Override + public WormPoint getPoint() { + return new WormPoint(getRunning().clone(), getRadius(), config.getCutTop(), config.getCutBottom()); + } + + @Override + public synchronized void step() { + if(steps == nextDirection) { + direction.rotateAroundX(FastMath.toRadians((getRandom().nextGaussian()) * mutate[0] * recalcMagnitude)); + direction.rotateAroundY(FastMath.toRadians((getRandom().nextGaussian()) * mutate[1] * recalcMagnitude)); + direction.rotateAroundZ(FastMath.toRadians((getRandom().nextGaussian()) * mutate[2] * recalcMagnitude)); + currentRotation = new double[] {(getRandom().nextGaussian()) * mutate[0], + (getRandom().nextGaussian()) * mutate[1], + (getRandom().nextGaussian()) * mutate[2]}; + nextDirection += recalc.get(getRandom()); + } + steps++; + position.setValue(steps); + setRadius(new int[] {(int) (xRad.evaluate()), (int) (yRad.evaluate()), (int) (zRad.evaluate())}); + direction.rotateAroundX(FastMath.toRadians(currentRotation[0] * mutate[0])); + direction.rotateAroundY(FastMath.toRadians(currentRotation[1] * mutate[1])); + direction.rotateAroundZ(FastMath.toRadians(currentRotation[2] * mutate[2])); + getRunning().add(direction); + } + } +} diff --git a/common/src/main/java/com/dfsek/terra/config/GenericLoaders.java b/common/src/main/java/com/dfsek/terra/config/GenericLoaders.java index 076a26a70..1b5c88e24 100644 --- a/common/src/main/java/com/dfsek/terra/config/GenericLoaders.java +++ b/common/src/main/java/com/dfsek/terra/config/GenericLoaders.java @@ -14,6 +14,7 @@ import com.dfsek.terra.api.world.palette.holder.PaletteLayerHolder; import com.dfsek.terra.biome.pipeline.stages.ExpanderStage; import com.dfsek.terra.biome.pipeline.stages.MutatorStage; +import com.dfsek.terra.carving.CarverPalette; import com.dfsek.terra.config.loaders.LinkedHashMapLoader; import com.dfsek.terra.config.loaders.MaterialSetLoader; import com.dfsek.terra.config.loaders.ProbabilityCollectionLoader; @@ -36,6 +37,7 @@ import com.dfsek.terra.config.loaders.config.sampler.templates.ImageSamplerTemplate; import com.dfsek.terra.config.loaders.config.sampler.templates.normalizer.LinearNormalizerTemplate; import com.dfsek.terra.config.loaders.config.sampler.templates.normalizer.NormalNormalizerTemplate; +import com.dfsek.terra.config.loaders.palette.CarverPaletteLoader; import com.dfsek.terra.config.loaders.palette.PaletteHolderLoader; import com.dfsek.terra.config.loaders.palette.PaletteLayerLoader; import com.dfsek.terra.util.MaterialSet; @@ -83,6 +85,7 @@ public void register(TypeRegistry registry) { .registerLoader(BorderListMutatorTemplate.class, BorderListMutatorTemplate::new) .registerLoader(FunctionTemplate.class, FunctionTemplate::new) .registerLoader(LinkedHashMap.class, new LinkedHashMapLoader()) + .registerLoader(CarverPalette.class, new CarverPaletteLoader()) .registerLoader(ImageSampler.Channel.class, (t, object, cf) -> ImageSampler.Channel.valueOf((String) object)) .registerLoader(ExpanderStage.Type.class, (t, object, cf) -> ExpanderStage.Type.valueOf((String) object)) .registerLoader(MutatorStage.Type.class, (t, object, cf) -> MutatorStage.Type.valueOf((String) object)) diff --git a/common/src/main/java/com/dfsek/terra/config/factories/CarverFactory.java b/common/src/main/java/com/dfsek/terra/config/factories/CarverFactory.java new file mode 100644 index 000000000..dc38d1c05 --- /dev/null +++ b/common/src/main/java/com/dfsek/terra/config/factories/CarverFactory.java @@ -0,0 +1,37 @@ +package com.dfsek.terra.config.factories; + +import com.dfsek.tectonic.exception.LoadException; +import com.dfsek.terra.api.core.TerraPlugin; +import com.dfsek.terra.api.math.MathUtil; +import com.dfsek.terra.carving.UserDefinedCarver; +import com.dfsek.terra.config.pack.ConfigPack; +import com.dfsek.terra.config.templates.CarverTemplate; +import parsii.tokenizer.ParseException; + +import java.util.Arrays; +import java.util.List; + +public class CarverFactory implements TerraFactory { + private final ConfigPack pack; + + public CarverFactory(ConfigPack pack) { + this.pack = pack; + } + + @Override + public UserDefinedCarver build(CarverTemplate config, TerraPlugin main) throws LoadException { + double[] start = new double[] {config.getStartX(), config.getStartY(), config.getStartZ()}; + double[] mutate = new double[] {config.getMutateX(), config.getMutateY(), config.getMutateZ()}; + List radius = Arrays.asList(config.getRadMX(), config.getRadMY(), config.getRadMZ()); + long hash = MathUtil.hashToLong(config.getID()); + UserDefinedCarver carver; + try { + carver = new UserDefinedCarver(config.getHeight(), config.getLength(), start, mutate, radius, pack.getVarScope(), hash, config.getCutTop(), config.getCutBottom(), config, main, pack.getTemplate().getNoiseBuilderMap(), pack.getTemplate().getFunctions()); + } catch(ParseException e) { + throw new LoadException("Unable to parse radius equations", e); + } + carver.setRecalc(config.getRecalc()); + carver.setRecalcMagnitude(config.getRecaclulateMagnitude()); + return carver; + } +} diff --git a/common/src/main/java/com/dfsek/terra/config/loaders/palette/CarverPaletteLoader.java b/common/src/main/java/com/dfsek/terra/config/loaders/palette/CarverPaletteLoader.java new file mode 100644 index 000000000..ed5bd12fb --- /dev/null +++ b/common/src/main/java/com/dfsek/terra/config/loaders/palette/CarverPaletteLoader.java @@ -0,0 +1,33 @@ +package com.dfsek.terra.config.loaders.palette; + +import com.dfsek.tectonic.config.Configuration; +import com.dfsek.tectonic.exception.LoadException; +import com.dfsek.tectonic.loading.ConfigLoader; +import com.dfsek.tectonic.loading.TypeLoader; +import com.dfsek.terra.api.math.ProbabilityCollection; +import com.dfsek.terra.api.platform.block.BlockData; +import com.dfsek.terra.carving.CarverPalette; +import com.dfsek.terra.config.loaders.Types; +import com.dfsek.terra.util.MaterialSet; + +import java.lang.reflect.Type; +import java.util.List; +import java.util.Map; + +@SuppressWarnings("unchecked") +public class CarverPaletteLoader implements TypeLoader { + + + @Override + public CarverPalette load(Type type, Object o, ConfigLoader configLoader) throws LoadException { + Configuration configuration = new Configuration((Map) o); + CarverPalette palette = new CarverPalette((MaterialSet) configLoader.loadType(MaterialSet.class, configuration.get("replace")), (Boolean) configuration.get("replace-blacklist")); + + for(Map map : (List>) configuration.get("layers")) { + ProbabilityCollection layer = (ProbabilityCollection) configLoader.loadType(Types.BLOCK_DATA_PROBABILITY_COLLECTION_TYPE, map.get("materials")); + palette.add(layer, (Integer) map.get("y")); + } + palette.build(); + return palette; + } +} diff --git a/common/src/main/java/com/dfsek/terra/config/pack/ConfigPack.java b/common/src/main/java/com/dfsek/terra/config/pack/ConfigPack.java index 6b587e8bc..d65d4aee9 100644 --- a/common/src/main/java/com/dfsek/terra/config/pack/ConfigPack.java +++ b/common/src/main/java/com/dfsek/terra/config/pack/ConfigPack.java @@ -14,8 +14,10 @@ import com.dfsek.terra.api.world.tree.Tree; import com.dfsek.terra.biome.TerraBiome; import com.dfsek.terra.biome.provider.BiomeProvider; +import com.dfsek.terra.carving.UserDefinedCarver; import com.dfsek.terra.config.exception.FileMissingException; import com.dfsek.terra.config.factories.BiomeFactory; +import com.dfsek.terra.config.factories.CarverFactory; import com.dfsek.terra.config.factories.FloraFactory; import com.dfsek.terra.config.factories.OreFactory; import com.dfsek.terra.config.factories.PaletteFactory; @@ -30,12 +32,14 @@ import com.dfsek.terra.config.loaders.config.biome.BiomeProviderBuilderLoader; import com.dfsek.terra.config.templates.AbstractableTemplate; import com.dfsek.terra.config.templates.BiomeTemplate; +import com.dfsek.terra.config.templates.CarverTemplate; import com.dfsek.terra.config.templates.FloraTemplate; import com.dfsek.terra.config.templates.OreTemplate; import com.dfsek.terra.config.templates.PaletteTemplate; import com.dfsek.terra.config.templates.StructureTemplate; import com.dfsek.terra.config.templates.TreeTemplate; import com.dfsek.terra.registry.BiomeRegistry; +import com.dfsek.terra.registry.CarverRegistry; import com.dfsek.terra.registry.FloraRegistry; import com.dfsek.terra.registry.LootRegistry; import com.dfsek.terra.registry.OreRegistry; @@ -82,6 +86,8 @@ public class ConfigPack implements LoaderRegistrar { private final ScriptRegistry scriptRegistry = new ScriptRegistry(); private final LootRegistry lootRegistry = new LootRegistry(); + private final CarverRegistry carverRegistry = new CarverRegistry(); + private final AbstractConfigLoader abstractConfigLoader = new AbstractConfigLoader(); private final ConfigLoader selfLoader = new ConfigLoader(); private final Scope varScope = new Scope(); @@ -180,14 +186,13 @@ private void load(long start, TerraPlugin main) throws ConfigException { }).close(); loader + .open("carving", ".yml").then(streams -> buildAll(new CarverFactory(this), carverRegistry, abstractConfigLoader.load(streams, CarverTemplate::new), main)).close() .open("palettes", ".yml").then(streams -> buildAll(new PaletteFactory(), paletteRegistry, abstractConfigLoader.load(streams, PaletteTemplate::new), main)).close() .open("ores", ".yml").then(streams -> buildAll(new OreFactory(), oreRegistry, abstractConfigLoader.load(streams, OreTemplate::new), main)).close() .open("structures/trees", ".yml").then(streams -> buildAll(new TreeFactory(), treeRegistry, abstractConfigLoader.load(streams, TreeTemplate::new), main)).close() .open("structures/structures", ".yml").then(streams -> buildAll(new StructureFactory(), structureRegistry, abstractConfigLoader.load(streams, StructureTemplate::new), main)).close() .open("flora", ".yml").then(streams -> buildAll(new FloraFactory(), floraRegistry, abstractConfigLoader.load(streams, FloraTemplate::new), main)).close() .open("biomes", ".yml").then(streams -> buildAll(new BiomeFactory(this), biomeRegistry, abstractConfigLoader.load(streams, () -> new BiomeTemplate(this, main)), main)).close(); - - main.packPostLoadCallback(this); LangUtil.log("config-pack.loaded", Level.INFO, template.getID(), String.valueOf((System.nanoTime() - start) / 1000000D), template.getAuthor(), template.getVersion()); } @@ -240,6 +245,7 @@ public void register(TypeRegistry registry) { .registerLoader(StructureScript.class, scriptRegistry) .registerLoader(TerraStructure.class, structureRegistry) .registerLoader(LootTable.class, lootRegistry) + .registerLoader(UserDefinedCarver.class, carverRegistry) .registerLoader(BufferedImage.class, new BufferedImageLoader(loader)) .registerLoader(BiomeProvider.BiomeProviderBuilder.class, new BiomeProviderBuilderLoader(main, biomeRegistry, loader)); } @@ -255,4 +261,8 @@ public BiomeRegistry getBiomeRegistry() { public SamplerCache getSamplerCache() { return samplerCache; } + + public Set getCarvers() { + return carverRegistry.entries(); + } } diff --git a/common/src/main/java/com/dfsek/terra/config/pack/ConfigPackTemplate.java b/common/src/main/java/com/dfsek/terra/config/pack/ConfigPackTemplate.java index cb16612cc..051bf659d 100644 --- a/common/src/main/java/com/dfsek/terra/config/pack/ConfigPackTemplate.java +++ b/common/src/main/java/com/dfsek/terra/config/pack/ConfigPackTemplate.java @@ -23,6 +23,10 @@ public class ConfigPackTemplate implements ConfigTemplate { @Default private Map variables = new HashMap<>(); + @Value("beta.carving") + @Default + private boolean betaCarvers = false; + @Value("functions") @Default private LinkedHashMap functions = new LinkedHashMap<>(); @@ -121,4 +125,8 @@ public int getElevationBlend() { public Map getLocatable() { return locatable; } + + public boolean doBetaCarvers() { + return betaCarvers; + } } diff --git a/common/src/main/java/com/dfsek/terra/config/templates/BiomeTemplate.java b/common/src/main/java/com/dfsek/terra/config/templates/BiomeTemplate.java index 2898a318e..d72f796de 100644 --- a/common/src/main/java/com/dfsek/terra/config/templates/BiomeTemplate.java +++ b/common/src/main/java/com/dfsek/terra/config/templates/BiomeTemplate.java @@ -19,6 +19,7 @@ import com.dfsek.terra.api.world.palette.Palette; import com.dfsek.terra.api.world.palette.SinglePalette; import com.dfsek.terra.api.world.palette.holder.PaletteHolder; +import com.dfsek.terra.carving.UserDefinedCarver; import com.dfsek.terra.config.loaders.config.function.FunctionTemplate; import com.dfsek.terra.config.loaders.config.sampler.templates.FastNoiseTemplate; import com.dfsek.terra.config.pack.ConfigPack; @@ -60,7 +61,7 @@ public class BiomeTemplate extends AbstractableTemplate implements ValidatedConf @Abstractable private LinkedHashMap functions = new LinkedHashMap<>(); - @Value("carving.equation") + @Value("beta.carving.equation") @Abstractable @Default private String carvingEquation = "0"; @@ -186,10 +187,19 @@ public class BiomeTemplate extends AbstractableTemplate implements ValidatedConf @Abstractable private Set tags; + @Value("carving") + @Abstractable + @Default + private Map carvers = new HashMap<>(); + public Set getTags() { return tags; } + public Map getCarvers() { + return carvers; + } + public Map getFunctions() { return functions; } diff --git a/common/src/main/java/com/dfsek/terra/population/CavePopulator.java b/common/src/main/java/com/dfsek/terra/population/CavePopulator.java new file mode 100644 index 000000000..720918abe --- /dev/null +++ b/common/src/main/java/com/dfsek/terra/population/CavePopulator.java @@ -0,0 +1,105 @@ +package com.dfsek.terra.population; + +import com.dfsek.terra.api.core.TerraPlugin; +import com.dfsek.terra.api.math.vector.Location; +import com.dfsek.terra.api.platform.block.Block; +import com.dfsek.terra.api.platform.block.BlockData; +import com.dfsek.terra.api.platform.block.MaterialData; +import com.dfsek.terra.api.platform.handle.WorldHandle; +import com.dfsek.terra.api.platform.world.Chunk; +import com.dfsek.terra.api.platform.world.World; +import com.dfsek.terra.api.world.generation.TerraBlockPopulator; +import com.dfsek.terra.carving.UserDefinedCarver; +import com.dfsek.terra.config.pack.ConfigPack; +import com.dfsek.terra.config.templates.CarverTemplate; +import com.dfsek.terra.profiler.ProfileFuture; +import com.dfsek.terra.util.PopulationUtil; +import com.dfsek.terra.world.TerraWorld; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Random; +import java.util.Set; + +public class CavePopulator implements TerraBlockPopulator { + private static final Map shiftStorage = new HashMap<>(); // Persist BlockData created for shifts, to avoid re-calculating each time. + private final TerraPlugin main; + + public CavePopulator(TerraPlugin main) { + this.main = main; + } + + @SuppressWarnings("try") + @Override + public void populate(@NotNull World world, @NotNull Chunk chunk) { + TerraWorld tw = main.getWorld(world); + WorldHandle handle = main.getWorldHandle(); + BlockData AIR = handle.createBlockData("minecraft:air"); + try(ProfileFuture ignored = tw.getProfiler().measure("CaveTime")) { + Random random = PopulationUtil.getRandom(chunk); + if(!tw.isSafe()) return; + ConfigPack config = tw.getConfig(); + + for(UserDefinedCarver c : config.getCarvers()) { + CarverTemplate template = c.getConfig(); + Map shiftCandidate = new HashMap<>(); + Set updateNeeded = new HashSet<>(); + c.carve(chunk.getX(), chunk.getZ(), world, (v, type) -> { + Block b = chunk.getBlock(v.getBlockX(), v.getBlockY(), v.getBlockZ()); + MaterialData m = handle.getType(b); + switch(type) { + case CENTER: + if(template.getInner().canReplace(m)) { + handle.setBlockData(b, template.getInner().get(v.getBlockY()).get(random), false); + if(template.getUpdate().contains(m)) updateNeeded.add(b); + if(template.getShift().containsKey(m)) shiftCandidate.put(b.getLocation(), m); + } + break; + case WALL: + if(template.getOuter().canReplace(m)) { + handle.setBlockData(b, template.getOuter().get(v.getBlockY()).get(random), false); + if(template.getUpdate().contains(m)) updateNeeded.add(b); + if(template.getShift().containsKey(m)) shiftCandidate.put(b.getLocation(), m); + } + break; + case TOP: + if(template.getTop().canReplace(m)) { + handle.setBlockData(b, template.getTop().get(v.getBlockY()).get(random), false); + if(template.getUpdate().contains(m)) updateNeeded.add(b); + if(template.getShift().containsKey(m)) shiftCandidate.put(b.getLocation(), m); + } + break; + case BOTTOM: + if(template.getBottom().canReplace(m)) { + handle.setBlockData(b, template.getBottom().get(v.getBlockY()).get(random), false); + if(template.getUpdate().contains(m)) updateNeeded.add(b); + if(template.getShift().containsKey(m)) shiftCandidate.put(b.getLocation(), m); + } + break; + } + }); + for(Map.Entry entry : shiftCandidate.entrySet()) { + Location l = entry.getKey(); + Location mut = l.clone(); + MaterialData orig = handle.getType(l.getBlock()); + do mut.subtract(0, 1, 0); + while(mut.getY() > 0 && handle.getType(mut.getBlock()).equals(orig)); + try { + if(template.getShift().get(entry.getValue()).contains(mut.getBlock().getType())) { + handle.setBlockData(mut.getBlock(), shiftStorage.computeIfAbsent(entry.getValue(), MaterialData::createBlockData), false); + } + } catch(NullPointerException ignore) { + } + } + for(Block b : updateNeeded) { + BlockData orig = handle.getBlockData(b); + handle.setBlockData(b, AIR, false); + handle.setBlockData(b, orig, true); + } + } + + } + } +} diff --git a/common/src/main/java/com/dfsek/terra/registry/CarverRegistry.java b/common/src/main/java/com/dfsek/terra/registry/CarverRegistry.java new file mode 100644 index 000000000..8de7fa2da --- /dev/null +++ b/common/src/main/java/com/dfsek/terra/registry/CarverRegistry.java @@ -0,0 +1,6 @@ +package com.dfsek.terra.registry; + +import com.dfsek.terra.carving.UserDefinedCarver; + +public class CarverRegistry extends TerraRegistry { +} diff --git a/common/src/main/java/com/dfsek/terra/world/generation/MasterChunkGenerator.java b/common/src/main/java/com/dfsek/terra/world/generation/MasterChunkGenerator.java index 8bb1124a1..4f74ccdb4 100644 --- a/common/src/main/java/com/dfsek/terra/world/generation/MasterChunkGenerator.java +++ b/common/src/main/java/com/dfsek/terra/world/generation/MasterChunkGenerator.java @@ -144,7 +144,9 @@ public ChunkGenerator.ChunkData generateChunkData(@NotNull World world, Random r } } } - carver.carve(world, chunkX, chunkZ, chunk); + if(configPack.getTemplate().doBetaCarvers()) { + carver.carve(world, chunkX, chunkZ, chunk); + } return chunk; } } diff --git a/platforms/bukkit/src/main/java/com/dfsek/terra/bukkit/generator/BukkitChunkGeneratorWrapper.java b/platforms/bukkit/src/main/java/com/dfsek/terra/bukkit/generator/BukkitChunkGeneratorWrapper.java index 47e8401be..b8bb83839 100644 --- a/platforms/bukkit/src/main/java/com/dfsek/terra/bukkit/generator/BukkitChunkGeneratorWrapper.java +++ b/platforms/bukkit/src/main/java/com/dfsek/terra/bukkit/generator/BukkitChunkGeneratorWrapper.java @@ -9,6 +9,7 @@ import com.dfsek.terra.bukkit.world.BukkitAdapter; import com.dfsek.terra.bukkit.world.BukkitBiomeGrid; import com.dfsek.terra.config.lang.LangUtil; +import com.dfsek.terra.population.CavePopulator; import com.dfsek.terra.profiler.DataType; import com.dfsek.terra.profiler.Measurement; import com.dfsek.terra.world.TerraWorld; @@ -52,6 +53,7 @@ public BukkitChunkGeneratorWrapper(TerraChunkGenerator delegate) { popMan.attach(new OrePopulator(main)); popMan.attach(new TreePopulator(main)); popMan.attach(new FloraPopulator(main)); + populators.add(new CavePopulator(main)); populators.add(new StructurePopulator(main)); populators.add(popMan); }