271 lines
12 KiB
Java
271 lines
12 KiB
Java
package com.hypixel.hytale.server.worldgen.cave;
|
|
|
|
import com.hypixel.hytale.math.util.FastRandom;
|
|
import com.hypixel.hytale.math.util.HashUtil;
|
|
import com.hypixel.hytale.math.util.MathUtil;
|
|
import com.hypixel.hytale.math.vector.Vector3d;
|
|
import com.hypixel.hytale.procedurallib.condition.ConstantIntCondition;
|
|
import com.hypixel.hytale.procedurallib.condition.DefaultCoordinateCondition;
|
|
import com.hypixel.hytale.procedurallib.condition.ICoordinateCondition;
|
|
import com.hypixel.hytale.procedurallib.condition.IIntCondition;
|
|
import com.hypixel.hytale.procedurallib.supplier.IDoubleRange;
|
|
import com.hypixel.hytale.server.core.prefab.PrefabRotation;
|
|
import com.hypixel.hytale.server.worldgen.cave.element.CaveNode;
|
|
import com.hypixel.hytale.server.worldgen.cave.element.CavePrefab;
|
|
import com.hypixel.hytale.server.worldgen.cave.prefab.CavePrefabContainer;
|
|
import com.hypixel.hytale.server.worldgen.cave.shape.CaveNodeShape;
|
|
import com.hypixel.hytale.server.worldgen.cave.shape.PrefabCaveNodeShape;
|
|
import com.hypixel.hytale.server.worldgen.chunk.ChunkGenerator;
|
|
import com.hypixel.hytale.server.worldgen.chunk.ZoneBiomeResult;
|
|
import com.hypixel.hytale.server.worldgen.loader.WorldGenPrefabSupplier;
|
|
import com.hypixel.hytale.server.worldgen.util.ArrayUtli;
|
|
import com.hypixel.hytale.server.worldgen.util.condition.flag.Int2FlagsCondition;
|
|
import java.util.Random;
|
|
import javax.annotation.Nonnull;
|
|
import javax.annotation.Nullable;
|
|
|
|
public class CaveGenerator {
|
|
private final CaveType[] caveTypes;
|
|
|
|
public CaveGenerator(CaveType[] caveTypes) {
|
|
this.caveTypes = caveTypes;
|
|
}
|
|
|
|
public CaveType[] getCaveTypes() {
|
|
return this.caveTypes;
|
|
}
|
|
|
|
@Nonnull
|
|
public Cave generate(int seed, @Nonnull ChunkGenerator chunkGenerator, @Nonnull CaveType caveType, int x, int y, int z) {
|
|
int seedOffset = (int)HashUtil.rehash(seed, x, y, z);
|
|
Random random = new FastRandom(seedOffset);
|
|
Cave cave = this.newCave(caveType);
|
|
Vector3d origin = new Vector3d(x, y, z);
|
|
origin.y = caveType.getModifiedStartHeight(seed + seedOffset, x, y, z, random);
|
|
this.startCave(seed, chunkGenerator, cave, origin, random);
|
|
cave.compile();
|
|
return cave;
|
|
}
|
|
|
|
@Nonnull
|
|
protected Cave newCave(CaveType caveType) {
|
|
return new Cave(caveType);
|
|
}
|
|
|
|
protected void startCave(int seed, @Nonnull ChunkGenerator chunkGenerator, @Nonnull Cave cave, @Nonnull Vector3d origin, @Nonnull Random random) {
|
|
Int2FlagsCondition biomeMask = cave.getCaveType().getBiomeMask();
|
|
int startBiomeMaskResult = this.getBiomeMaskResult(seed, chunkGenerator, biomeMask, origin);
|
|
if (CaveBiomeMaskFlags.canGenerate(startBiomeMaskResult)) {
|
|
CaveType caveType = cave.getCaveType();
|
|
int depth = caveType.getStartDepth(random);
|
|
CaveNodeType type = caveType.getEntryNode();
|
|
float yaw = caveType.getStartYaw(random);
|
|
float pitch = caveType.getStartPitch(random);
|
|
int seedOffset = random.nextInt();
|
|
CaveNodeShape shape = type.generateCaveNodeShape(random, caveType, null, null, origin, yaw, pitch);
|
|
int endBiomeMaskResult = this.getBiomeMaskResult(seed, chunkGenerator, biomeMask, shape.getEnd());
|
|
if (CaveBiomeMaskFlags.canGenerate(endBiomeMaskResult)) {
|
|
CaveNode node = new CaveNode(seed + seedOffset, type, shape, yaw, pitch);
|
|
if (shape.hasGeometry() && CaveBiomeMaskFlags.canPopulate(startBiomeMaskResult) && CaveBiomeMaskFlags.canPopulate(endBiomeMaskResult)) {
|
|
cave.addNode(node);
|
|
}
|
|
|
|
this.continueNode(seed, chunkGenerator, cave, node, depth, random);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected void continueNode(
|
|
int seed, @Nonnull ChunkGenerator chunkGenerator, @Nonnull Cave cave, @Nonnull CaveNode parent, int depth, @Nonnull Random random
|
|
) {
|
|
if (depth > 0) {
|
|
Int2FlagsCondition biomeMask = cave.getCaveType().getBiomeMask();
|
|
CaveNodeType.CaveNodeChildEntry[] childEntries = this.getChildEntriesRandomized(parent.getCaveNodeType(), random);
|
|
int childrenCount = this.getChildrenCount(parent.getCaveNodeType(), random);
|
|
int generatedChildren = 0;
|
|
|
|
for (CaveNodeType.CaveNodeChildEntry childEntry : childEntries) {
|
|
int repeat = this.getRepeatCounter(childEntry, random);
|
|
|
|
for (int j = 0; j < repeat; j++) {
|
|
if (this.shouldGenerateChild(childEntry, random)) {
|
|
if (generatedChildren >= childrenCount) {
|
|
return;
|
|
}
|
|
|
|
PrefabRotation parentRotation = this.getRotation(parent);
|
|
Vector3d origin = this.getChildOrigin(parent, parentRotation, childEntry);
|
|
CaveNodeType type = childEntry.getTypes().get(random);
|
|
if (this.isMatchingHeight(seed, origin, type.getHeightCondition())) {
|
|
float yaw = this.getChildYaw(parent, parentRotation, childEntry, random);
|
|
float pitch = childEntry.getPitchModifier().calc(parent.getPitch(), random);
|
|
int hash = random.nextInt();
|
|
CaveNodeShape shape = type.generateCaveNodeShape(random, cave.getCaveType(), parent, childEntry, origin, yaw, pitch);
|
|
if (this.isMatchingHeight(seed, shape.getEnd(), type.getHeightCondition())) {
|
|
int biomeMaskResult = this.getBiomeMaskResult(seed, chunkGenerator, biomeMask, shape.getEnd());
|
|
if (!CaveBiomeMaskFlags.canGenerate(biomeMaskResult)) {
|
|
if (!CaveBiomeMaskFlags.canContinue(biomeMaskResult)) {
|
|
break;
|
|
}
|
|
} else {
|
|
CaveNode node = new CaveNode(hash, type, shape, yaw, pitch);
|
|
if (shape.hasGeometry() && CaveBiomeMaskFlags.canPopulate(biomeMaskResult)) {
|
|
this.generatePrefabs(seed, chunkGenerator, parent, node);
|
|
cave.addNode(node);
|
|
}
|
|
|
|
int nextDepth = this.getNextDepth(childEntry, depth, random);
|
|
this.continueNode(seed, chunkGenerator, cave, node, nextDepth, random);
|
|
generatedChildren++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
protected int getChildrenCount(@Nonnull CaveNodeType type, Random random) {
|
|
IDoubleRange countArray = type.getChildrenCountBounds();
|
|
return countArray == null ? Integer.MAX_VALUE : MathUtil.floor(countArray.getValue(random));
|
|
}
|
|
|
|
@Nonnull
|
|
protected CaveNodeType.CaveNodeChildEntry[] getChildEntriesRandomized(@Nonnull CaveNodeType type, @Nonnull Random random) {
|
|
CaveNodeType.CaveNodeChildEntry[] childEntries = type.getChildren();
|
|
if (type.getChildrenCountBounds() != null && childEntries.length != 0) {
|
|
CaveNodeType.CaveNodeChildEntry[] randomized = new CaveNodeType.CaveNodeChildEntry[childEntries.length];
|
|
System.arraycopy(childEntries, 0, randomized, 0, randomized.length);
|
|
ArrayUtli.shuffleArray((CaveNodeType.CaveNodeChildEntry[])randomized, random);
|
|
return randomized;
|
|
} else {
|
|
return childEntries;
|
|
}
|
|
}
|
|
|
|
protected int getRepeatCounter(@Nonnull CaveNodeType.CaveNodeChildEntry entry, Random random) {
|
|
return MathUtil.floor(entry.getRepeat().getValue(random));
|
|
}
|
|
|
|
@Nullable
|
|
protected PrefabRotation getRotation(@Nonnull CaveNode caveNode) {
|
|
CaveNodeShape shape = caveNode.getShape();
|
|
return shape instanceof PrefabCaveNodeShape ? ((PrefabCaveNodeShape)shape).getPrefabRotation() : null;
|
|
}
|
|
|
|
protected Vector3d getChildOrigin(@Nonnull CaveNode parentNode, @Nullable PrefabRotation parentRotation, @Nonnull CaveNodeType.CaveNodeChildEntry childEntry) {
|
|
Vector3d vector = parentNode.getEnd();
|
|
Vector3d anchor = childEntry.getAnchor();
|
|
if (anchor == Vector3d.ZERO) {
|
|
return vector;
|
|
} else {
|
|
vector.assign(anchor);
|
|
if (parentRotation != null && parentRotation != PrefabRotation.ROTATION_0) {
|
|
vector.subtract(0.5, 0.5, 0.5);
|
|
parentRotation.rotate(vector);
|
|
vector.add(0.5, 0.5, 0.5);
|
|
}
|
|
|
|
return parentNode.getShape().getAnchor(vector, vector.x, vector.y, vector.z);
|
|
}
|
|
}
|
|
|
|
protected float getChildYaw(
|
|
@Nonnull CaveNode parentNode, @Nullable PrefabRotation parentRotation, @Nonnull CaveNodeType.CaveNodeChildEntry childEntry, Random random
|
|
) {
|
|
float yaw = childEntry.getYawMode().combine(parentNode.getYaw(), parentRotation);
|
|
return childEntry.getYawModifier().calc(yaw, random);
|
|
}
|
|
|
|
protected boolean shouldGenerateChild(@Nonnull CaveNodeType.CaveNodeChildEntry entry, @Nonnull Random random) {
|
|
return random.nextDouble() < entry.getChance();
|
|
}
|
|
|
|
protected boolean isMatchingHeight(int seed, @Nonnull Vector3d vec, @Nonnull ICoordinateCondition condition) {
|
|
if (condition == DefaultCoordinateCondition.DEFAULT_TRUE) {
|
|
return true;
|
|
} else if (condition == DefaultCoordinateCondition.DEFAULT_FALSE) {
|
|
return false;
|
|
} else {
|
|
int x = MathUtil.floor(vec.x);
|
|
int y = MathUtil.floor(vec.y);
|
|
int z = MathUtil.floor(vec.z);
|
|
return condition.eval(seed, x, y, z);
|
|
}
|
|
}
|
|
|
|
protected int getNextDepth(@Nonnull CaveNodeType.CaveNodeChildEntry entry, int depth, Random random) {
|
|
int nextDepth = depth - 1;
|
|
if (entry.getChildrenLimit() != null) {
|
|
int limit = MathUtil.floor(entry.getChildrenLimit().getValue(random));
|
|
if (limit < nextDepth) {
|
|
return limit;
|
|
}
|
|
}
|
|
|
|
return nextDepth;
|
|
}
|
|
|
|
protected void generatePrefabs(int seed, @Nonnull ChunkGenerator chunkGenerator, CaveNode parent, @Nonnull CaveNode node) {
|
|
Random random = ChunkGenerator.getResource().getRandom();
|
|
random.setSeed(seed + node.getSeedOffset());
|
|
CavePrefabContainer container = node.getCaveNodeType().getPrefabContainer();
|
|
if (container != null) {
|
|
for (CavePrefabContainer.CavePrefabEntry entry : container.getEntries()) {
|
|
this.generatePrefab(seed, chunkGenerator, parent, node, entry, random);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected void generatePrefab(
|
|
int seed,
|
|
@Nonnull ChunkGenerator chunkGenerator,
|
|
@Nullable CaveNode parent,
|
|
@Nonnull CaveNode caveNode,
|
|
@Nonnull CavePrefabContainer.CavePrefabEntry entry,
|
|
@Nonnull Random random
|
|
) {
|
|
CavePrefabContainer.CavePrefabEntry.CavePrefabConfig config = entry.getConfig();
|
|
int iterations = config.getIterations(random.nextDouble());
|
|
|
|
for (int i = 0; i < iterations; i++) {
|
|
int x = caveNode.getBounds().randomX(random);
|
|
int z = caveNode.getBounds().randomZ(random);
|
|
if (this.isMatchingBiome(seed, chunkGenerator, config.getBiomeMask(), x, z) && config.isMatchingNoiseDensity(seed, x, z)) {
|
|
int y = config.getHeight(seed, x, z, caveNode);
|
|
if (y != -1 && config.isMatchingHeight(seed, x, y, z, random) && (parent == null || !parent.getShape().shouldReplace(seed, x, z, y))) {
|
|
WorldGenPrefabSupplier prefab = entry.getPrefab(random.nextDouble());
|
|
PrefabRotation rotation = config.getRotation(random);
|
|
CavePrefab entity = new CavePrefab(prefab, rotation, config.getBiomeMask(), config.getBlockMask(), x, y, z);
|
|
caveNode.addPrefab(entity);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
protected boolean isMatchingBiome(int seed, @Nonnull ChunkGenerator chunkGenerator, @Nonnull IIntCondition condition, int x, int z) {
|
|
if (condition == ConstantIntCondition.DEFAULT_TRUE) {
|
|
return true;
|
|
} else if (condition == ConstantIntCondition.DEFAULT_FALSE) {
|
|
return false;
|
|
} else {
|
|
ZoneBiomeResult biomeResult = chunkGenerator.getZoneBiomeResultAt(seed, x, z);
|
|
return condition.eval(biomeResult.getBiome().getId());
|
|
}
|
|
}
|
|
|
|
protected int getBiomeMaskResult(int seed, @Nonnull ChunkGenerator chunkGenerator, @Nonnull Int2FlagsCondition mask, @Nonnull Vector3d vec) {
|
|
if (mask == CaveBiomeMaskFlags.DEFAULT_ALLOW) {
|
|
return 7;
|
|
} else if (mask == CaveBiomeMaskFlags.DEFAULT_DENY) {
|
|
return 0;
|
|
} else {
|
|
int x = MathUtil.floor(vec.getX());
|
|
int z = MathUtil.floor(vec.getZ());
|
|
ZoneBiomeResult biomeResult = chunkGenerator.getZoneBiomeResultAt(seed, x, z);
|
|
return mask.eval(biomeResult.getBiome().getId());
|
|
}
|
|
}
|
|
}
|