Public Access
Add the writable symbol, adapt Legacy's branching solid cave-node geometry across all terrain types, and test authored and random Age selection.
This commit is contained in:
1 parent
bb88bad8a6
commit
a9040e8712
11 files changed
+260
-20
No files matched your search
@@ -24,7 +24,8 @@ import net.minecraft.world.level.block.Blocks;
|
||||
|
||||
/** Validates and freezes supported feature pages and their preceding material modifiers. */
|
||||
public final class AgeFeatureSelection {
|
||||
private static final List<ResourceLocation> BLOCK_FEATURES = List.of(id("rocks"), id("floating_islands"));
|
||||
private static final List<ResourceLocation> BLOCK_FEATURES = List.of(
|
||||
id("rocks"), id("floating_islands"), id("tendrils"));
|
||||
private static final List<ResourceLocation> FLUID_FEATURES = List.of(
|
||||
id("surface_lakes"), id("deep_lakes"), id("lava_lakes"));
|
||||
|
||||
@@ -112,6 +113,7 @@ public final class AgeFeatureSelection {
|
||||
}
|
||||
|
||||
private static boolean compatible(ResourceLocation feature, AgeRuntimeProfile profile) {
|
||||
if (tendrils(feature)) return true;
|
||||
if (AgeBiomeSelection.isCave(profile)) return false;
|
||||
return floatingIslands(feature) || profile.terrain() == AgeRuntimeProfile.Terrain.NOISE;
|
||||
}
|
||||
@@ -124,7 +126,8 @@ public final class AgeFeatureSelection {
|
||||
for (Choice choice : choices(plan.symbols().stream().map(symbol -> symbol.semantics()).toList(),
|
||||
plan.runtimeProfile(), plan.ageSeed())) {
|
||||
if (choice.material().isPresent()
|
||||
|| !isRock(choice.id()) && !isLake(choice.id()) && !floatingIslands(choice.id())) {
|
||||
|| !isRock(choice.id()) && !isLake(choice.id())
|
||||
&& !floatingIslands(choice.id()) && !tendrils(choice.id())) {
|
||||
resolved.add(choice);
|
||||
continue;
|
||||
}
|
||||
@@ -147,7 +150,7 @@ public final class AgeFeatureSelection {
|
||||
List<Choice> choices = new ArrayList<>();
|
||||
for (ResourceLocation feature : features) {
|
||||
if (choices.size() >= count) break;
|
||||
var category = isRock(feature) || floatingIslands(feature)
|
||||
var category = isRock(feature) || floatingIslands(feature) || tendrils(feature)
|
||||
? BlockSymbolPages.CATEGORY : BlockSymbolPages.FLUID_CATEGORY;
|
||||
List<ResourceLocation> materials = BlockSymbolPages.availableBlocks(category).stream()
|
||||
.filter(id -> materialError(new Choice(feature, Optional.of(id))) == null).toList();
|
||||
@@ -158,7 +161,7 @@ public final class AgeFeatureSelection {
|
||||
}
|
||||
|
||||
private static List<ResourceLocation> shuffledMaterials(long ageSeed, ResourceLocation feature) {
|
||||
var category = isRock(feature) || floatingIslands(feature)
|
||||
var category = isRock(feature) || floatingIslands(feature) || tendrils(feature)
|
||||
? BlockSymbolPages.CATEGORY : BlockSymbolPages.FLUID_CATEGORY;
|
||||
List<ResourceLocation> pool = new ArrayList<>(BlockSymbolPages.availableBlocks(category).stream()
|
||||
.filter(id -> materialError(new Choice(feature, Optional.of(id))) == null).toList());
|
||||
@@ -178,6 +181,7 @@ public final class AgeFeatureSelection {
|
||||
}
|
||||
|
||||
public static String error(ResourceLocation choice, AgeRuntimeProfile profile, RegistryAccess registries) {
|
||||
if (tendrils(choice)) return null;
|
||||
if (floatingIslands(choice)) {
|
||||
if (AgeBiomeSelection.isCave(profile))
|
||||
return "Feature " + choice + " cannot form above Cave terrain";
|
||||
@@ -216,7 +220,7 @@ public final class AgeFeatureSelection {
|
||||
if (block.isEmpty() || block.orElseThrow() == Blocks.AIR) {
|
||||
return "Feature " + choice.id() + " references missing block material " + material;
|
||||
}
|
||||
if (isRock(choice.id()) || floatingIslands(choice.id())) {
|
||||
if (isRock(choice.id()) || floatingIslands(choice.id()) || tendrils(choice.id())) {
|
||||
if (!BlockSymbolPages.isRockMaterial(block.orElseThrow())) {
|
||||
return "Feature " + choice.id() + " needs a full solid block without a block entity: " + material;
|
||||
}
|
||||
@@ -256,6 +260,10 @@ public final class AgeFeatureSelection {
|
||||
return choice.equals(ResourceLocation.fromNamespaceAndPath("rivencraft", "floating_islands"));
|
||||
}
|
||||
|
||||
public static boolean tendrils(ResourceLocation choice) {
|
||||
return choice.equals(id("tendrils"));
|
||||
}
|
||||
|
||||
public static boolean supportsSurfaceVegetation(ResourceLocation id, RegistryAccess registries) {
|
||||
var biome = registries.lookupOrThrow(Registries.BIOME)
|
||||
.get(ResourceKey.create(Registries.BIOME, id));
|
||||
|
||||
@@ -32,7 +32,7 @@ public final class FeatureWritingGameTests {
|
||||
Set<String> pages = Set.of("feature/caves", "feature/flowers", "feature/trees",
|
||||
"feature/huge_trees", "feature/rocks", "feature/lava_lakes",
|
||||
"feature/surface_lakes", "feature/deep_lakes", "feature/ravines",
|
||||
"feature/floating_islands");
|
||||
"feature/floating_islands", "feature/tendrils");
|
||||
helper.assertTrue(PageItem.bundledCreativeSymbols().containsAll(pages),
|
||||
"Supported feature pages must appear in the creative and JEI symbol feed");
|
||||
for (String path : pages) {
|
||||
@@ -59,7 +59,8 @@ public final class FeatureWritingGameTests {
|
||||
AgeFeatureSelection.resolved(plan), "Features must survive the saved-plan round trip");
|
||||
for (AgeFeatureSelection.Choice choice : AgeFeatureSelection.resolved(plan)) {
|
||||
if (AgeFeatureSelection.caves(choice.id()) || AgeFeatureSelection.ravines(choice.id())
|
||||
|| AgeFeatureSelection.floatingIslands(choice.id())) continue;
|
||||
|| AgeFeatureSelection.floatingIslands(choice.id())
|
||||
|| AgeFeatureSelection.tendrils(choice.id())) continue;
|
||||
var placement = AgeFeatureSelection.placedFeature(choice.id());
|
||||
helper.assertTrue(registries.lookupOrThrow(Registries.PLACED_FEATURE)
|
||||
.get(ResourceKey.create(Registries.PLACED_FEATURE, placement)).isPresent(),
|
||||
@@ -136,6 +137,47 @@ public final class FeatureWritingGameTests {
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void tendrilsAreAuthoredAndCanAppearInRandomAges(GameTestHelper helper) {
|
||||
var registries = helper.getLevel().registryAccess();
|
||||
AgeRuntimeProfiles.profiles().forEach((profileId, profile) ->
|
||||
helper.assertTrue(AgeFeatureSelection.error(id("tendrils"), profile, registries) == null,
|
||||
"Tendrils must support loaded terrain profile " + profileId));
|
||||
for (String terrain : List.of("standard", "amplified", "island", "flat", "void", "cave")) {
|
||||
OrderedWriting writing = new OrderedWriting(List.of(panel(), page("terrain/" + terrain),
|
||||
page("feature/tendrils")));
|
||||
AgeGenerationPlan plan = AgeGenerationPlan.compile(writing, 450L, AgeGenerationProfiles.STANDARD,
|
||||
AgeTraits.select(450L), registries);
|
||||
List<AgeFeatureSelection.Choice> choices = AgeFeatureSelection.resolved(plan);
|
||||
helper.assertValueEqual(choices.size(), 1, "Tendrils must compile on " + terrain + " terrain");
|
||||
helper.assertValueEqual(choices.getFirst().id(), id("tendrils"),
|
||||
"Tendrils must not replace the terrain page");
|
||||
helper.assertTrue(choices.getFirst().material().isPresent()
|
||||
&& AgeFeatureSelection.materialError(choices.getFirst()) == null,
|
||||
"Unmodified Tendrils must choose a full-block material");
|
||||
helper.assertValueEqual(AgeFeatureSelection.resolved(AgeGenerationPlan.load(plan.save())), choices,
|
||||
"The Tendrils material must survive a plan reload");
|
||||
}
|
||||
OrderedWriting materialWriting = new OrderedWriting(List.of(panel(), page("terrain/void"),
|
||||
block("glowstone"), page("feature/tendrils")));
|
||||
AgeGenerationPlan materialPlan = AgeGenerationPlan.compile(materialWriting, 450L,
|
||||
AgeGenerationProfiles.STANDARD, AgeTraits.select(450L), registries);
|
||||
helper.assertValueEqual(AgeFeatureSelection.resolved(materialPlan), List.of(
|
||||
new AgeFeatureSelection.Choice(id("tendrils"), Optional.of(
|
||||
ResourceLocation.withDefaultNamespace("glowstone")))),
|
||||
"A preceding Block page must set the Tendrils material");
|
||||
var caveProfile = AgeRuntimeProfiles.resolve(id("cave"));
|
||||
boolean found = false;
|
||||
for (long seed = 0; seed < 12; seed++) {
|
||||
List<AgeFeatureSelection.Choice> random = AgeFeatureSelection.randomChoices(seed, caveProfile, registries);
|
||||
helper.assertTrue(random.stream().allMatch(choice -> AgeFeatureSelection.tendrils(choice.id())),
|
||||
"Cave random Ages must not choose incompatible surface features");
|
||||
if (!random.isEmpty()) found = true;
|
||||
}
|
||||
helper.assertTrue(found, "A page-free random Cave Age must be able to roll Tendrils");
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void voidBiomesAreIndependentOfFloatingIslands(GameTestHelper helper) {
|
||||
OrderedWriting voidDefault = new OrderedWriting(List.of(panel(), page("terrain/void")));
|
||||
@@ -275,7 +317,8 @@ public final class FeatureWritingGameTests {
|
||||
helper.assertTrue(material.equals("water")
|
||||
? AgeFeatureSelection.isLake(choices.getFirst().id())
|
||||
: AgeFeatureSelection.isRock(choices.getFirst().id())
|
||||
|| AgeFeatureSelection.floatingIslands(choices.getFirst().id()),
|
||||
|| AgeFeatureSelection.floatingIslands(choices.getFirst().id())
|
||||
|| AgeFeatureSelection.tendrils(choices.getFirst().id()),
|
||||
"The inferred feature must accept the material type");
|
||||
helper.assertValueEqual(AgeFeatureSelection.resolved(AgeGenerationPlan.load(plan.save())), choices,
|
||||
"The inferred feature must be deterministic across a plan reload");
|
||||
|
||||
@@ -75,13 +75,18 @@ public final class AgeFeatureChunkGenerator extends NoiseBasedChunkGenerator {
|
||||
public CompletableFuture<ChunkAccess> fillFromNoise(Blender blender, RandomState random,
|
||||
StructureManager structures, ChunkAccess chunk) {
|
||||
CompletableFuture<ChunkAccess> filled = super.fillFromNoise(blender, random, structures, chunk);
|
||||
if (features.stream().noneMatch(choice -> AgeFeatureSelection.floatingIslands(choice.id()))) return filled;
|
||||
if (features.stream().noneMatch(choice -> AgeFeatureSelection.floatingIslands(choice.id())
|
||||
|| AgeFeatureSelection.tendrils(choice.id()))) return filled;
|
||||
return filled.thenApply(result -> {
|
||||
int occurrence = 0;
|
||||
Map<ResourceLocation, Integer> occurrences = new HashMap<>();
|
||||
for (AgeFeatureSelection.Choice choice : features) {
|
||||
if (!AgeFeatureSelection.floatingIslands(choice.id())) continue;
|
||||
FloatingIslandFeature.generate(result, getBiomeSource(), random,
|
||||
ageSeed ^ (long) occurrence++ * 0x9E3779B97F4A7C15L, choice);
|
||||
if (!AgeFeatureSelection.floatingIslands(choice.id())
|
||||
&& !AgeFeatureSelection.tendrils(choice.id())) continue;
|
||||
int occurrence = occurrences.merge(choice.id(), 1, Integer::sum) - 1;
|
||||
long featureSeed = ageSeed ^ (long) occurrence * 0x9E3779B97F4A7C15L;
|
||||
if (AgeFeatureSelection.floatingIslands(choice.id()))
|
||||
FloatingIslandFeature.generate(result, getBiomeSource(), random, featureSeed, choice);
|
||||
else TendrilFeature.generate(result, featureSeed, choice);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
@@ -3,7 +3,9 @@ package net.dnorberg.rivencraft.world;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Stream;
|
||||
import net.dnorberg.rivencraft.book.AgeFeatureSelection;
|
||||
@@ -12,6 +14,7 @@ import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.core.HolderSet;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.WorldGenRegion;
|
||||
import net.minecraft.world.level.LevelHeightAccessor;
|
||||
import net.minecraft.world.level.NoiseColumn;
|
||||
@@ -97,11 +100,15 @@ public final class AgeFlatChunkGenerator extends ChunkGenerator {
|
||||
}
|
||||
}
|
||||
}
|
||||
int islandOccurrence = 0;
|
||||
Map<ResourceLocation, Integer> occurrences = new HashMap<>();
|
||||
for (AgeFeatureSelection.Choice choice : features) {
|
||||
if (!AgeFeatureSelection.floatingIslands(choice.id())) continue;
|
||||
FloatingIslandFeature.generate(chunk, biomeSource, random,
|
||||
ageSeed ^ (long) islandOccurrence++ * 0x9E3779B97F4A7C15L, choice);
|
||||
if (!AgeFeatureSelection.floatingIslands(choice.id())
|
||||
&& !AgeFeatureSelection.tendrils(choice.id())) continue;
|
||||
int occurrence = occurrences.merge(choice.id(), 1, Integer::sum) - 1;
|
||||
long featureSeed = ageSeed ^ (long) occurrence * 0x9E3779B97F4A7C15L;
|
||||
if (AgeFeatureSelection.floatingIslands(choice.id()))
|
||||
FloatingIslandFeature.generate(chunk, biomeSource, random, featureSeed, choice);
|
||||
else TendrilFeature.generate(chunk, featureSeed, choice);
|
||||
}
|
||||
return CompletableFuture.completedFuture(chunk);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
package net.dnorberg.rivencraft.world;
|
||||
|
||||
import java.util.Random;
|
||||
import net.dnorberg.rivencraft.book.AgeFeatureSelection;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.levelgen.Heightmap;
|
||||
|
||||
/**
|
||||
* Winding solid volumes adapted from Mystcraft Legacy's SymbolTendrils and
|
||||
* MapGenCavesMyst (LGPL-3.0-or-later). Generation stays inside the target chunk,
|
||||
* while source-chunk seeds make the same tendril meet seamlessly at its borders.
|
||||
*/
|
||||
final class TendrilFeature {
|
||||
private static final long SALT = 0x54454E4452494C53L;
|
||||
private static final int SOURCE_RANGE = 8;
|
||||
private static final int SOURCE_RATE = 15;
|
||||
private static final int SOURCE_SIZE = 18;
|
||||
|
||||
private TendrilFeature() {}
|
||||
|
||||
static void generate(ChunkAccess chunk, long ageSeed, AgeFeatureSelection.Choice choice) {
|
||||
BlockState material = choice.material()
|
||||
.map(id -> BuiltInRegistries.BLOCK.get(id).defaultBlockState())
|
||||
.orElse(Blocks.OAK_LOG.defaultBlockState());
|
||||
ChunkPos target = chunk.getPos();
|
||||
Random master = new Random(ageSeed ^ SALT);
|
||||
long xSeed = master.nextLong();
|
||||
long zSeed = master.nextLong();
|
||||
for (int sourceX = target.x - SOURCE_RANGE; sourceX <= target.x + SOURCE_RANGE; sourceX++) {
|
||||
for (int sourceZ = target.z - SOURCE_RANGE; sourceZ <= target.z + SOURCE_RANGE; sourceZ++) {
|
||||
Random source = new Random((long) sourceX * xSeed ^ (long) sourceZ * zSeed ^ ageSeed ^ SALT);
|
||||
int nodes = source.nextInt(source.nextInt(source.nextInt(SOURCE_SIZE) + 1) + 1);
|
||||
if (source.nextInt(SOURCE_RATE) != 0) continue;
|
||||
for (int node = 0; node < nodes; node++) {
|
||||
double x = sourceX * 16 + source.nextInt(16);
|
||||
double y = source.nextInt(source.nextInt(120) + 8);
|
||||
double z = sourceZ * 16 + source.nextInt(16);
|
||||
int branches = 1;
|
||||
if (source.nextInt(4) == 0) {
|
||||
trace(chunk, material, source.nextLong(), x, y, z,
|
||||
1.0F + source.nextFloat() * 6.0F, 0.0F, 0.0F, -1, -1, 0.5D);
|
||||
branches += source.nextInt(4);
|
||||
}
|
||||
for (int branch = 0; branch < branches; branch++) {
|
||||
float yaw = source.nextFloat() * Mth.TWO_PI;
|
||||
float pitch = (source.nextFloat() - 0.5F) / 4.0F;
|
||||
float radius = source.nextFloat() * 2.0F + source.nextFloat();
|
||||
if (source.nextInt(10) == 0)
|
||||
radius *= source.nextFloat() * source.nextFloat() * 3.0F + 1.0F;
|
||||
trace(chunk, material, source.nextLong(), x, y, z,
|
||||
radius, yaw, pitch, 0, 0, 1.0D);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void trace(ChunkAccess chunk, BlockState material, long seed,
|
||||
double x, double y, double z, float radius, float yaw, float pitch,
|
||||
int step, int length, double verticalScale) {
|
||||
Random random = new Random(seed);
|
||||
if (length <= 0) {
|
||||
int maximum = SOURCE_RANGE * 16 - 16;
|
||||
length = maximum - random.nextInt(maximum / 4);
|
||||
}
|
||||
boolean singleNode = step == -1;
|
||||
if (singleNode) step = length / 2;
|
||||
int splitAt = random.nextInt(length / 2) + length / 4;
|
||||
boolean slowPitch = random.nextInt(6) == 0;
|
||||
float yawDrift = 0.0F;
|
||||
float pitchDrift = 0.0F;
|
||||
double middleX = chunk.getPos().getMinBlockX() + 8;
|
||||
double middleZ = chunk.getPos().getMinBlockZ() + 8;
|
||||
|
||||
for (; step < length; step++) {
|
||||
double horizontalRadius = 1.5D + Math.sin(step * Math.PI / length) * radius;
|
||||
double verticalRadius = horizontalRadius * verticalScale;
|
||||
float horizontalMotion = Mth.cos(pitch);
|
||||
x += Mth.cos(yaw) * horizontalMotion;
|
||||
y += Mth.sin(pitch);
|
||||
z += Mth.sin(yaw) * horizontalMotion;
|
||||
pitch *= slowPitch ? 0.92F : 0.7F;
|
||||
pitch += pitchDrift * 0.1F;
|
||||
yaw += yawDrift * 0.1F;
|
||||
pitchDrift = pitchDrift * 0.9F
|
||||
+ (random.nextFloat() - random.nextFloat()) * random.nextFloat() * 2.0F;
|
||||
yawDrift = yawDrift * 0.75F
|
||||
+ (random.nextFloat() - random.nextFloat()) * random.nextFloat() * 4.0F;
|
||||
|
||||
if (!singleNode && step == splitAt && radius > 1.0F) {
|
||||
trace(chunk, material, random.nextLong(), x, y, z, random.nextFloat() * 0.5F + 0.5F,
|
||||
yaw - Mth.HALF_PI, pitch / 3.0F, step, length, 1.0D);
|
||||
trace(chunk, material, random.nextLong(), x, y, z, random.nextFloat() * 0.5F + 0.5F,
|
||||
yaw + Mth.HALF_PI, pitch / 3.0F, step, length, 1.0D);
|
||||
return;
|
||||
}
|
||||
if (!singleNode && random.nextInt(4) == 0) continue;
|
||||
double distanceX = x - middleX;
|
||||
double distanceZ = z - middleZ;
|
||||
double remaining = length - step;
|
||||
double reach = radius + 18.0D;
|
||||
if (distanceX * distanceX + distanceZ * distanceZ - remaining * remaining > reach * reach)
|
||||
return;
|
||||
if (x >= middleX - 16.0D - horizontalRadius * 2.0D
|
||||
&& x <= middleX + 16.0D + horizontalRadius * 2.0D
|
||||
&& z >= middleZ - 16.0D - horizontalRadius * 2.0D
|
||||
&& z <= middleZ + 16.0D + horizontalRadius * 2.0D) {
|
||||
fillNode(chunk, material, x, y, z, horizontalRadius, verticalRadius);
|
||||
}
|
||||
if (singleNode) break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void fillNode(ChunkAccess chunk, BlockState material, double centerX, double centerY,
|
||||
double centerZ, double horizontalRadius, double verticalRadius) {
|
||||
int originX = chunk.getPos().getMinBlockX();
|
||||
int originZ = chunk.getPos().getMinBlockZ();
|
||||
int minX = Math.max(0, Mth.floor(centerX - horizontalRadius) - originX - 1);
|
||||
int maxX = Math.min(16, Mth.floor(centerX + horizontalRadius) - originX + 2);
|
||||
int minZ = Math.max(0, Mth.floor(centerZ - horizontalRadius) - originZ - 1);
|
||||
int maxZ = Math.min(16, Mth.floor(centerZ + horizontalRadius) - originZ + 2);
|
||||
int minY = Math.max(chunk.getMinBuildHeight() + 1, Mth.floor(centerY - verticalRadius) - 1);
|
||||
int maxY = Math.min(chunk.getMaxBuildHeight(), Mth.floor(centerY + verticalRadius) + 2);
|
||||
Heightmap surface = chunk.getOrCreateHeightmapUnprimed(Heightmap.Types.WORLD_SURFACE_WG);
|
||||
Heightmap floor = chunk.getOrCreateHeightmapUnprimed(Heightmap.Types.OCEAN_FLOOR_WG);
|
||||
BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos();
|
||||
for (int worldY = minY; worldY < maxY; worldY++) {
|
||||
double dy = (worldY + 0.5D - centerY) / verticalRadius;
|
||||
if (dy <= -0.7D || dy * dy >= 1.0D) continue;
|
||||
for (int localZ = minZ; localZ < maxZ; localZ++) {
|
||||
double dz = (originZ + localZ + 0.5D - centerZ) / horizontalRadius;
|
||||
for (int localX = minX; localX < maxX; localX++) {
|
||||
double dx = (originX + localX + 0.5D - centerX) / horizontalRadius;
|
||||
if (dx * dx + dy * dy + dz * dz >= 1.0D) continue;
|
||||
pos.set(originX + localX, worldY, originZ + localZ);
|
||||
if (chunk.getBlockState(pos).is(Blocks.BEDROCK)) continue;
|
||||
chunk.setBlockState(pos, material, false);
|
||||
surface.update(localX, worldY, localZ, material);
|
||||
floor.update(localX, worldY, localZ, material);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,7 @@ public final class WritingGrammar {
|
||||
pendingBlock = parsed;
|
||||
} else if (role == Role.FEATURE && pendingBlock != null) {
|
||||
ResourceLocation feature = ((SymbolSemantics.ResourceValue) parsed.semantics().value()).id();
|
||||
if (!Set.of("rocks", "surface_lakes", "deep_lakes", "lava_lakes", "floating_islands")
|
||||
if (!Set.of("rocks", "surface_lakes", "deep_lakes", "lava_lakes", "floating_islands", "tendrils")
|
||||
.contains(feature.getPath()) || !feature.getNamespace().equals("rivencraft")) {
|
||||
diagnostics.add(new Diagnostic(Code.MISPLACED_MODIFIER, pendingBlock.index(),
|
||||
"This feature cannot consume a material page"));
|
||||
|
||||
@@ -245,6 +245,8 @@
|
||||
"symbol.rivencraft.feature.ravines.description": "Add vanilla canyon carving to noise terrain.",
|
||||
"symbol.rivencraft.feature.floating_islands": "Floating Islands",
|
||||
"symbol.rivencraft.feature.floating_islands.description": "Add high floating islands above the chosen surface terrain.",
|
||||
"symbol.rivencraft.feature.tendrils": "Tendrils",
|
||||
"symbol.rivencraft.feature.tendrils.description": "Add winding solid tendrils through the Age, using a random block or a preceding Block page.",
|
||||
"symbol.rivencraft.feature.lava_lakes": "Small Lakes",
|
||||
"symbol.rivencraft.feature.lava_lakes.description": "Add small surface lakes using a random fluid, or specify one with a fluid page.",
|
||||
"generator.rivencraft.standard": "Standard",
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"schema_version": 2,
|
||||
"name": "symbol.rivencraft.feature.tendrils",
|
||||
"description": "symbol.rivencraft.feature.tendrils.description",
|
||||
"category": "rivencraft:feature",
|
||||
"semantics": { "role": "feature", "value": "rivencraft:tendrils" },
|
||||
"poem": "Terrain Thread Structure Winding"
|
||||
}
|
||||
Reference in new issue
Block a user