Public Access
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c89cfc464 |
No files matched your search
@@ -28,6 +28,7 @@ project's GNU LGPL v3 license. Rivencraft itself uses the same license.
|
|||||||
| `assets/rivencraft/textures/block/crystal_block.png`, `budding_crystal.png` | `assets/mystcraft/textures/blocks/crystal.png` | Legacy 0.13.7.06 local extraction | Exact 16×16 blue tile reused for both solid crystal blocks |
|
| `assets/rivencraft/textures/block/crystal_block.png`, `budding_crystal.png` | `assets/mystcraft/textures/blocks/crystal.png` | Legacy 0.13.7.06 local extraction | Exact 16×16 blue tile reused for both solid crystal blocks |
|
||||||
| `assets/rivencraft/textures/block/{small,medium,large}_crystal_bud.png`, `crystal_cluster.png` | Minecraft 1.21.1 `textures/block/{small,medium,large}_amethyst_bud.png`, `amethyst_cluster.png` | Minecraft 1.21.1 | Original 16×16 alpha masks and silhouettes preserved; hue shifted toward Legacy blue, with brighter saturation; exported as 8-bit RGBA (not indexed PNG) |
|
| `assets/rivencraft/textures/block/{small,medium,large}_crystal_bud.png`, `crystal_cluster.png` | Minecraft 1.21.1 `textures/block/{small,medium,large}_amethyst_bud.png`, `amethyst_cluster.png` | Minecraft 1.21.1 | Original 16×16 alpha masks and silhouettes preserved; hue shifted toward Legacy blue, with brighter saturation; exported as 8-bit RGBA (not indexed PNG) |
|
||||||
| `assets/rivencraft/textures/item/crystal_shard.png` | Original sprite generated with OpenAI ImageGen from the Legacy crystal palette | 2026-10-05 | Cropped and nearest-neighbor scaled to 16×16 |
|
| `assets/rivencraft/textures/item/crystal_shard.png` | Original sprite generated with OpenAI ImageGen from the Legacy crystal palette | 2026-10-05 | Cropped and nearest-neighbor scaled to 16×16 |
|
||||||
|
| `assets/rivencraft/sounds/linking/link-fissure.ogg` | `assets/mystcraft/sounds/linking/link-fissure.ogg` | Legacy 0.13.7.06 local extraction | Namespace/path rename only; plays when a Star Fissure returns an entity home |
|
||||||
|
|
||||||
Mystcraft Legacy identifies XCompWiz as its project developer and includes the
|
Mystcraft Legacy identifies XCompWiz as its project developer and includes the
|
||||||
full LGPL v3 text in its root `LICENSE` file. Rivencraft's root `LICENSE`
|
full LGPL v3 text in its root `LICENSE` file. Rivencraft's root `LICENSE`
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ Legacy does **not** reject a book just because it omits terrain, biome, weather,
|
|||||||
| `WeatherNorm`, `Fast`, `Slow`, `Off`, `On`, `Cloudy`, `Rain`, `Snow`, `Storm` | `AgeTraits.Weather` has matching names except Legacy `On` maps to `ALWAYS` | Compile weather semantics separately from biome selection. `Snow` must remain deferred until optional season integration; do not silently substitute another weather mode. |
|
| `WeatherNorm`, `Fast`, `Slow`, `Off`, `On`, `Cloudy`, `Rain`, `Snow`, `Storm` | `AgeTraits.Weather` has matching names except Legacy `On` maps to `ALWAYS` | Compile weather semantics separately from biome selection. `Snow` must remain deferred until optional season integration; do not silently substitute another weather mode. |
|
||||||
| Suns, moons, starfields; angle, phase, period, color/gradient/sunset modifiers | One `celestialPeriod`, `celestialAngle`, `celestialPhase` on `AgeTraits` | Compile only the representable single-cycle subset first. Multiple celestial bodies, independent periods, custom stars, gradients and sunsets need a richer definition and renderer, not flattening into one value. |
|
| Suns, moons, starfields; angle, phase, period, color/gradient/sunset modifiers | One `celestialPeriod`, `celestialAngle`, `celestialPhase` on `AgeTraits` | Compile only the representable single-cycle subset first. Multiple celestial bodies, independent periods, custom stars, gradients and sunsets need a richer definition and renderer, not flattening into one value. |
|
||||||
| Sky/fog/cloud/grass/foliage/water colors, natural variants, horizon, rainbow | Existing sky/dimension visuals and preview | Require an explicit modern client/biome-color design; metadata-only symbol pages must not pretend to change rendering. |
|
| Sky/fog/cloud/grass/foliage/water colors, natural variants, horizon, rainbow | Existing sky/dimension visuals and preview | Require an explicit modern client/biome-color design; metadata-only symbol pages must not pretend to change rendering. |
|
||||||
| Caves, ores, huge trees, lakes, floating islands, tendrils, skylands, spheres, spikes, crystals; vanilla villages, strongholds, mineshafts, dungeons, fortresses, obelisks, fissures | Current cave profile, authored Floating Islands/Tendrils/Skylands/Spheres/Spikes/Obelisks/Crystal Formations and Dense Ores, and page-gated vanilla Dungeons/Mineshafts/Nether Fortresses/Strongholds/Villages | The five Legacy vanilla structure symbols gate Minecraft 1.21.1 generation. Dungeons use monster-room placement; the others use vanilla structure sets. Obelisks use a bespoke 4×4 plinth and 2×12×2 shaft with safe modern placement. Crystal Formations retain Legacy's blue tile and one-in-fifteen attempt rate but use new budding, shard-producing mounds. Fissures and exact 1.12 structure geometry remain separate work. |
|
| Caves, ores, huge trees, lakes, floating islands, tendrils, skylands, spheres, spikes, crystals; vanilla villages, strongholds, mineshafts, dungeons, fortresses, obelisks, fissures | Current cave profile, authored Floating Islands/Tendrils/Skylands/Spheres/Spikes/Obelisks/Crystal Formations/Star Fissures and Dense Ores, and page-gated vanilla Dungeons/Mineshafts/Nether Fortresses/Strongholds/Villages | The five Legacy vanilla structure symbols gate Minecraft 1.21.1 generation. Dungeons use monster-room placement; the others use vanilla structure sets. Obelisks use a bespoke 4×4 plinth and 2×12×2 shaft with safe modern placement. Crystal Formations retain Legacy's blue tile and one-in-fifteen attempt rate but use new budding, shard-producing mounds. Star Fissures reproduce Legacy's spawn-chunk footprint, open-to-sky columns, home link, and sound. Their renderer uses the modern End Portal pipeline in place of Legacy's retired fixed-function OpenGL calls. Exact 1.12 geometry for other structures remains separate work. |
|
||||||
| `EnvAccel`, explosions, lightning, meteors, scorching; PvP and dummy suppression pages | No equivalent Age-definition effect | Defer environmental hazards/instability effects to milestone 6, PvP rule to a separately scoped server policy; retain authored IDs without fake behavior. |
|
| `EnvAccel`, explosions, lightning, meteors, scorching; PvP and dummy suppression pages | No equivalent Age-definition effect | Defer environmental hazards/instability effects to milestone 6, PvP rule to a separately scoped server policy; retain authored IDs without fake behavior. |
|
||||||
|
|
||||||
The Legacy symbol list and page names come from [ModSymbols](https://github.com/Mystcraft/Mystcraft-Legacy/blob/9bc8ddc061845df0cd5ea47f4fdef773cff786ae/src/main/java/com/xcompwiz/mystcraft/data/ModSymbols.java#L129-L240) and [SymbolRules](https://github.com/Mystcraft/Mystcraft-Legacy/blob/9bc8ddc061845df0cd5ea47f4fdef773cff786ae/src/main/java/com/xcompwiz/mystcraft/data/SymbolRules.java#L19-L137). Rivencraft's [catalog](../src/main/java/net/dnorberg/rivencraft/writing/SymbolCatalog.java) currently holds display metadata only; its built-in set is just standard terrain, native biomes, normal weather, normal lighting, normal time, and caves. [OrderedWriting](../src/main/java/net/dnorberg/rivencraft/writing/OrderedWriting.java) preserves page order, but no compiler yet connects it to [AgeTraits](../src/main/java/net/dnorberg/rivencraft/book/AgeTraits.java) or [Age profiles](AGE_PROFILES.md).
|
The Legacy symbol list and page names come from [ModSymbols](https://github.com/Mystcraft/Mystcraft-Legacy/blob/9bc8ddc061845df0cd5ea47f4fdef773cff786ae/src/main/java/com/xcompwiz/mystcraft/data/ModSymbols.java#L129-L240) and [SymbolRules](https://github.com/Mystcraft/Mystcraft-Legacy/blob/9bc8ddc061845df0cd5ea47f4fdef773cff786ae/src/main/java/com/xcompwiz/mystcraft/data/SymbolRules.java#L19-L137). Rivencraft's [catalog](../src/main/java/net/dnorberg/rivencraft/writing/SymbolCatalog.java) currently holds display metadata only; its built-in set is just standard terrain, native biomes, normal weather, normal lighting, normal time, and caves. [OrderedWriting](../src/main/java/net/dnorberg/rivencraft/writing/OrderedWriting.java) preserves page order, but no compiler yet connects it to [AgeTraits](../src/main/java/net/dnorberg/rivencraft/book/AgeTraits.java) or [Age profiles](AGE_PROFILES.md).
|
||||||
|
|||||||
@@ -20,4 +20,6 @@ Material pages are generated for registered full-collision, non-fluid blocks and
|
|||||||
|
|
||||||
Crystal Formations are an authored, material-free feature on exposed solid terrain, including Flat terrain; Cave and Void reject the page. Like Legacy, one in fifteen eligible chunks attempts a formation, with the site fixed by the Age seed and chunk coordinates. The modern formation is a compact mound with budding hearts instead of Legacy's long crystal lines. Budding blocks grow through small, medium, and large buds into clusters using vanilla amethyst timing and sounds. Non-silk cluster harvest yields Crystal Shards; four shards craft one Crystal Block. These blocks and shards use 16×16 textures: Legacy's crystal tile for solids and hue-shifted vanilla amethyst sprites for buds and clusters. A page-free Age can also randomly select Crystal Formations; an authored page always retains its exact choice in the saved plan.
|
Crystal Formations are an authored, material-free feature on exposed solid terrain, including Flat terrain; Cave and Void reject the page. Like Legacy, one in fifteen eligible chunks attempts a formation, with the site fixed by the Age seed and chunk coordinates. The modern formation is a compact mound with budding hearts instead of Legacy's long crystal lines. Budding blocks grow through small, medium, and large buds into clusters using vanilla amethyst timing and sounds. Non-silk cluster harvest yields Crystal Shards; four shards craft one Crystal Block. These blocks and shards use 16×16 textures: Legacy's crystal tile for solids and hue-shifted vanilla amethyst sprites for buds and clusters. A page-free Age can also randomly select Crystal Formations; an authored page always retains its exact choice in the saved plan.
|
||||||
|
|
||||||
|
Star Fissures are a material-free feature on every terrain profile, including Cave and Void. The fissure is generated once beside the first resolved spawn, using Legacy's spawn-chunk offset and 10–17 paired, irregular X-span rows running along +Z. Each portal tile is placed at y=0 and its column is opened to the sky, as in Legacy. For modern terrain with a surface below y=0, the portal uses the minimum build height plus one instead; empty Void columns keep y=0. Its non-solid portal links to the Overworld spawn with the Legacy fissure sound; the animated starfield is drawn with Minecraft's current End Portal renderer, which uses the same end-sky/end-portal art as Legacy's custom renderer. Random Ages sample Legacy's default small-feature grammar for fissure selection: the stop/one/repeat branches have rank-derived weights 1:1:3, and the fissure has weight 1 of 9 among its small-feature rules. The remainder of random feature selection still follows Rivencraft's current pipeline, rather than running Legacy's entire Age grammar.
|
||||||
|
|
||||||
The Rivencraft symbol-page creative tabs read a generated index of bundled symbol definitions and group Pages by each definition's category, except biome-controller roles, which appear in a separate Biome Generation tab rather than the Biome tab. Adding another valid JSON symbol under `src/main/resources/data/rivencraft/rivencraft/symbols/` includes its page in the matching category tab on the next build; a new bundled category receives its own tab. Registry-derived Block and Fluid Pages have separate tabs and both appear in optional JEI. Link Pages have a separate tab. Blank Pages appear in neither the creative tabs nor JEI's ingredient list. The server catalog still decides whether a page is valid for writing. External server data-pack symbols are not included in this bundled index.
|
The Rivencraft symbol-page creative tabs read a generated index of bundled symbol definitions and group Pages by each definition's category, except biome-controller roles, which appear in a separate Biome Generation tab rather than the Biome tab. Adding another valid JSON symbol under `src/main/resources/data/rivencraft/rivencraft/symbols/` includes its page in the matching category tab on the next build; a new bundled category receives its own tab. Registry-derived Block and Fluid Pages have separate tabs and both appear in optional JEI. Link Pages have a separate tab. Blank Pages appear in neither the creative tabs nor JEI's ingredient list. The server catalog still decides whether a page is valid for writing. External server data-pack symbols are not included in this bundled index.
|
||||||
+1
-1
@@ -36,7 +36,7 @@ mod_name=Rivencraft
|
|||||||
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
|
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
|
||||||
mod_license=LGPL-3.0-or-later
|
mod_license=LGPL-3.0-or-later
|
||||||
# The mod version. Rivencraft uses the x.y.z.b policy documented in README.md.
|
# The mod version. Rivencraft uses the x.y.z.b policy documented in README.md.
|
||||||
mod_version=0.4.18.2
|
mod_version=0.4.19.1
|
||||||
immersive_portals_version=6.0.7
|
immersive_portals_version=6.0.7
|
||||||
terrablender_version=4.1.0.8
|
terrablender_version=4.1.0.8
|
||||||
serene_seasons_version_id=B6nTjh4b
|
serene_seasons_version_id=B6nTjh4b
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package net.dnorberg.rivencraft.block;
|
||||||
|
|
||||||
|
import com.mojang.serialization.MapCodec;
|
||||||
|
import net.dnorberg.rivencraft.block.entity.StarFissureBlockEntity;
|
||||||
|
import net.dnorberg.rivencraft.book.LinkingService;
|
||||||
|
import net.dnorberg.rivencraft.registry.ModSounds;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.sounds.SoundSource;
|
||||||
|
import net.minecraft.world.entity.Entity;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.level.BlockGetter;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.world.level.LevelReader;
|
||||||
|
import net.minecraft.world.level.block.BaseEntityBlock;
|
||||||
|
import net.minecraft.world.level.block.Block;
|
||||||
|
import net.minecraft.world.level.block.Portal;
|
||||||
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
|
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import net.minecraft.world.level.material.Fluid;
|
||||||
|
import net.minecraft.world.level.portal.DimensionTransition;
|
||||||
|
import net.minecraft.world.phys.Vec3;
|
||||||
|
import net.minecraft.world.phys.shapes.BooleanOp;
|
||||||
|
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||||
|
import net.minecraft.world.phys.shapes.Shapes;
|
||||||
|
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||||
|
|
||||||
|
/** A starry, non-obtainable portal at the base of a generated fissure. */
|
||||||
|
public final class StarFissureBlock extends BaseEntityBlock implements Portal {
|
||||||
|
public static final MapCodec<StarFissureBlock> CODEC = simpleCodec(StarFissureBlock::new);
|
||||||
|
private static final VoxelShape SHAPE = Block.box(0, 0, 0, 16, 1.6, 16);
|
||||||
|
|
||||||
|
public StarFissureBlock(BlockBehaviour.Properties properties) {
|
||||||
|
super(properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected MapCodec<? extends BaseEntityBlock> codec() {
|
||||||
|
return CODEC;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
|
||||||
|
return new StarFissureBlockEntity(pos, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
|
||||||
|
return SHAPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
|
||||||
|
if (entity.canUsePortal(false) && Shapes.joinIsNotEmpty(
|
||||||
|
Shapes.create(entity.getBoundingBox().move(-pos.getX(), -pos.getY(), -pos.getZ())),
|
||||||
|
state.getShape(level, pos), BooleanOp.AND)) {
|
||||||
|
entity.setAsInsidePortal(this, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DimensionTransition getPortalDestination(ServerLevel level, Entity entity, BlockPos pos) {
|
||||||
|
if (level.dimension() == Level.OVERWORLD) return null;
|
||||||
|
ServerLevel overworld = level.getServer().overworld();
|
||||||
|
if (overworld == null) return null;
|
||||||
|
Vec3 destination = LinkingService.resolveDestination(overworld, overworld.getSharedSpawnPos(), true)
|
||||||
|
.orElse(overworld.getSharedSpawnPos().getBottomCenter());
|
||||||
|
level.playSound(null, pos, ModSounds.LINK_FISSURE.get(), SoundSource.BLOCKS, 1.0F, 1.0F);
|
||||||
|
return new DimensionTransition(overworld, destination,
|
||||||
|
entity.getDeltaMovement(), entity.getYRot(), entity.getXRot(),
|
||||||
|
DimensionTransition.PLACE_PORTAL_TICKET);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ItemStack getCloneItemStack(LevelReader level, BlockPos pos, BlockState state) {
|
||||||
|
return ItemStack.EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean canBeReplaced(BlockState state, Fluid fluid) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package net.dnorberg.rivencraft.block.entity;
|
||||||
|
|
||||||
|
import net.dnorberg.rivencraft.registry.ModBlockEntities;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.world.level.block.entity.TheEndPortalBlockEntity;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
|
||||||
|
/** Reuses Minecraft's animated starfield renderer with a valid Rivencraft block-entity type. */
|
||||||
|
public final class StarFissureBlockEntity extends TheEndPortalBlockEntity {
|
||||||
|
public StarFissureBlockEntity(BlockPos pos, BlockState state) {
|
||||||
|
super(ModBlockEntities.STAR_FISSURE.get(), pos, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,6 +39,7 @@ import net.minecraft.world.level.biome.MultiNoiseBiomeSource;
|
|||||||
import net.dnorberg.rivencraft.world.ScaledBiomeSource;
|
import net.dnorberg.rivencraft.world.ScaledBiomeSource;
|
||||||
import net.dnorberg.rivencraft.world.AgeFlatChunkGenerator;
|
import net.dnorberg.rivencraft.world.AgeFlatChunkGenerator;
|
||||||
import net.dnorberg.rivencraft.world.AgeFeatureChunkGenerator;
|
import net.dnorberg.rivencraft.world.AgeFeatureChunkGenerator;
|
||||||
|
import net.dnorberg.rivencraft.world.StarFissureFeature;
|
||||||
import net.minecraft.world.level.chunk.ChunkGenerator;
|
import net.minecraft.world.level.chunk.ChunkGenerator;
|
||||||
import net.minecraft.world.level.dimension.BuiltinDimensionTypes;
|
import net.minecraft.world.level.dimension.BuiltinDimensionTypes;
|
||||||
import net.minecraft.world.level.dimension.DimensionType;
|
import net.minecraft.world.level.dimension.DimensionType;
|
||||||
@@ -98,6 +99,12 @@ public final class AgeDimensionService {
|
|||||||
if (spawn == null) {
|
if (spawn == null) {
|
||||||
applyInitialCelestialPhase(level, definition);
|
applyInitialCelestialPhase(level, definition);
|
||||||
spawn = createSafeSpawn(level, definition);
|
spawn = createSafeSpawn(level, definition);
|
||||||
|
if (AgeFeatureSelection.resolved(definition.plan()).stream()
|
||||||
|
.anyMatch(choice -> choice.id().equals(AgeFeatureSelection.STAR_FISSURES))
|
||||||
|
|| definition.randomFeatures().stream()
|
||||||
|
.anyMatch(choice -> choice.id().equals(AgeFeatureSelection.STAR_FISSURES))) {
|
||||||
|
StarFissureFeature.generate(level, definition.seed(), spawn);
|
||||||
|
}
|
||||||
definition = AgeRegistrySavedData.get(level).setSpawn(definition.id(), spawn);
|
definition = AgeRegistrySavedData.get(level).setSpawn(definition.id(), spawn);
|
||||||
}
|
}
|
||||||
return Optional.of(new LinkTarget(
|
return Optional.of(new LinkTarget(
|
||||||
|
|||||||
@@ -32,7 +32,9 @@ public final class AgeFeatureSelection {
|
|||||||
private static final List<ResourceLocation> FLUID_FEATURES = List.of(
|
private static final List<ResourceLocation> FLUID_FEATURES = List.of(
|
||||||
id("surface_lakes"), id("deep_lakes"), id("lava_lakes"));
|
id("surface_lakes"), id("deep_lakes"), id("lava_lakes"));
|
||||||
private static final long RANDOM_SKYLANDS_SALT = 0x534B594C414E4453L;
|
private static final long RANDOM_SKYLANDS_SALT = 0x534B594C414E4453L;
|
||||||
|
private static final long RANDOM_STAR_FISSURES_SALT = 0x535441524752414DL;
|
||||||
public static final ResourceLocation CRYSTAL_FORMATIONS = id("crystal_formations");
|
public static final ResourceLocation CRYSTAL_FORMATIONS = id("crystal_formations");
|
||||||
|
public static final ResourceLocation STAR_FISSURES = id("star_fissures");
|
||||||
|
|
||||||
private AgeFeatureSelection() {}
|
private AgeFeatureSelection() {}
|
||||||
|
|
||||||
@@ -176,12 +178,33 @@ public final class AgeFeatureSelection {
|
|||||||
choices.add(new Choice(id("skylands"), Optional.empty()));
|
choices.add(new Choice(id("skylands"), Optional.empty()));
|
||||||
if (error(CRYSTAL_FORMATIONS, profile, registries) == null && random.nextInt(10) == 0)
|
if (error(CRYSTAL_FORMATIONS, profile, registries) == null && random.nextInt(10) == 0)
|
||||||
choices.add(new Choice(CRYSTAL_FORMATIONS, Optional.empty()));
|
choices.add(new Choice(CRYSTAL_FORMATIONS, Optional.empty()));
|
||||||
|
if (randomStarFissure(ageSeed))
|
||||||
|
choices.add(new Choice(STAR_FISSURES, Optional.empty()));
|
||||||
AgeBiomeSelection biomes = AgeBiomeSelection.compile(WritingGrammar.parse(OrderedWriting.EMPTY),
|
AgeBiomeSelection biomes = AgeBiomeSelection.compile(WritingGrammar.parse(OrderedWriting.EMPTY),
|
||||||
AgeTraits.select(ageSeed), profile, ageSeed, registries);
|
AgeTraits.select(ageSeed), profile, ageSeed, registries);
|
||||||
choices.addAll(AgeStructureSelection.randomChoices(ageSeed, profile, biomes, registries));
|
choices.addAll(AgeStructureSelection.randomChoices(ageSeed, profile, biomes, registries));
|
||||||
return List.copyOf(choices);
|
return List.copyOf(choices);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Legacy's default FeatureSmalls0 grammar: stop without a feature (rank 4),
|
||||||
|
* emit one (rank 4), or emit one and repeat (rank 2). Its rank-derived
|
||||||
|
* weights are 1:1:3. Within FEATURE_SMALL, the Star Fissure rule has weight
|
||||||
|
* 1 of 9: three rank-3 choices have weight 1 each and two rank-1 lake
|
||||||
|
* choices have weight 3 each. Other small features remain selected by this
|
||||||
|
* port's existing random feature pipeline; only the fissure result is used.
|
||||||
|
*/
|
||||||
|
private static boolean randomStarFissure(long ageSeed) {
|
||||||
|
Random random = new Random(mix(ageSeed ^ RANDOM_STAR_FISSURES_SALT));
|
||||||
|
for (int step = 0; step < 27; step++) {
|
||||||
|
int branch = random.nextInt(5);
|
||||||
|
if (branch == 0) return false;
|
||||||
|
if (random.nextInt(9) == 0) return true;
|
||||||
|
if (branch == 1) return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private static List<ResourceLocation> shuffledMaterials(long ageSeed, ResourceLocation feature) {
|
private static List<ResourceLocation> shuffledMaterials(long ageSeed, ResourceLocation feature) {
|
||||||
var category = solidFeature(feature)
|
var category = solidFeature(feature)
|
||||||
? BlockSymbolPages.CATEGORY : BlockSymbolPages.FLUID_CATEGORY;
|
? BlockSymbolPages.CATEGORY : BlockSymbolPages.FLUID_CATEGORY;
|
||||||
@@ -212,6 +235,7 @@ public final class AgeFeatureSelection {
|
|||||||
return "Feature " + choice + " needs exposed solid ground, not Cave or Void terrain";
|
return "Feature " + choice + " needs exposed solid ground, not Cave or Void terrain";
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (choice.equals(STAR_FISSURES)) return null;
|
||||||
if (skylands(choice)) return null;
|
if (skylands(choice)) return null;
|
||||||
if (tendrils(choice)) return null;
|
if (tendrils(choice)) return null;
|
||||||
if (spheres(choice)) return null;
|
if (spheres(choice)) return null;
|
||||||
|
|||||||
@@ -79,11 +79,13 @@ public final class AgePersistenceGameTests {
|
|||||||
"A random Age may roll at most two distinct structure features");
|
"A random Age may roll at most two distinct structure features");
|
||||||
for (AgeFeatureSelection.Choice choice : age.randomFeatures()) {
|
for (AgeFeatureSelection.Choice choice : age.randomFeatures()) {
|
||||||
helper.assertTrue(AgeFeatureSelection.skylands(choice.id())
|
helper.assertTrue(AgeFeatureSelection.skylands(choice.id())
|
||||||
|
|| choice.id().equals(AgeFeatureSelection.CRYSTAL_FORMATIONS)
|
||||||
|
|| choice.id().equals(AgeFeatureSelection.STAR_FISSURES)
|
||||||
|| AgeStructureSelection.feature(choice.id())
|
|| AgeStructureSelection.feature(choice.id())
|
||||||
? choice.material().isEmpty()
|
? choice.material().isEmpty()
|
||||||
: choice.material().isPresent()
|
: choice.material().isPresent()
|
||||||
&& AgeFeatureSelection.materialError(choice) == null,
|
&& AgeFeatureSelection.materialError(choice) == null,
|
||||||
"Random material features need valid blocks or fluids; Skylands and structures need none");
|
"Random material features need valid blocks or fluids; material-free features need none");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AgeRegistrySavedData registry = AgeRegistrySavedData.get(helper.getLevel());
|
AgeRegistrySavedData registry = AgeRegistrySavedData.get(helper.getLevel());
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ public final class FeatureWritingGameTests {
|
|||||||
"feature/bastions", "feature/buried_treasure", "feature/shipwrecks",
|
"feature/bastions", "feature/buried_treasure", "feature/shipwrecks",
|
||||||
"feature/ocean_ruins", "feature/pillager_outposts", "feature/ruined_portals",
|
"feature/ocean_ruins", "feature/pillager_outposts", "feature/ruined_portals",
|
||||||
"feature/nether_fossils", "feature/ancient_cities", "feature/trail_ruins",
|
"feature/nether_fossils", "feature/ancient_cities", "feature/trail_ruins",
|
||||||
"feature/dense_ores", "feature/obelisks", "feature/crystal_formations");
|
"feature/dense_ores", "feature/obelisks", "feature/crystal_formations",
|
||||||
|
"feature/star_fissures");
|
||||||
helper.assertTrue(PageItem.bundledCreativeSymbols().containsAll(pages),
|
helper.assertTrue(PageItem.bundledCreativeSymbols().containsAll(pages),
|
||||||
"Supported feature pages must appear in the creative and JEI symbol feed");
|
"Supported feature pages must appear in the creative and JEI symbol feed");
|
||||||
for (String path : pages) {
|
for (String path : pages) {
|
||||||
@@ -74,6 +75,7 @@ public final class FeatureWritingGameTests {
|
|||||||
|| AgeFeatureSelection.spikes(choice.id())
|
|| AgeFeatureSelection.spikes(choice.id())
|
||||||
|| AgeFeatureSelection.obelisks(choice.id())
|
|| AgeFeatureSelection.obelisks(choice.id())
|
||||||
|| choice.id().equals(AgeFeatureSelection.CRYSTAL_FORMATIONS)
|
|| choice.id().equals(AgeFeatureSelection.CRYSTAL_FORMATIONS)
|
||||||
|
|| choice.id().equals(AgeFeatureSelection.STAR_FISSURES)
|
||||||
|| choice.id().equals(AgeDenseOres.ID)) continue;
|
|| choice.id().equals(AgeDenseOres.ID)) continue;
|
||||||
var placement = AgeFeatureSelection.placedFeature(choice.id());
|
var placement = AgeFeatureSelection.placedFeature(choice.id());
|
||||||
helper.assertTrue(registries.lookupOrThrow(Registries.PLACED_FEATURE)
|
helper.assertTrue(registries.lookupOrThrow(Registries.PLACED_FEATURE)
|
||||||
@@ -187,8 +189,9 @@ public final class FeatureWritingGameTests {
|
|||||||
helper.assertTrue(random.stream().allMatch(choice -> AgeFeatureSelection.tendrils(choice.id())
|
helper.assertTrue(random.stream().allMatch(choice -> AgeFeatureSelection.tendrils(choice.id())
|
||||||
|| AgeFeatureSelection.spheres(choice.id())
|
|| AgeFeatureSelection.spheres(choice.id())
|
||||||
|| AgeFeatureSelection.skylands(choice.id())
|
|| AgeFeatureSelection.skylands(choice.id())
|
||||||
|
|| choice.id().equals(AgeFeatureSelection.STAR_FISSURES)
|
||||||
|| AgeStructureSelection.feature(choice.id())),
|
|| AgeStructureSelection.feature(choice.id())),
|
||||||
"Cave random Ages must not choose incompatible surface features");
|
"Cave random Ages must only choose compatible features, including Star Fissures");
|
||||||
if (random.stream().anyMatch(choice -> AgeFeatureSelection.tendrils(choice.id()))) found = true;
|
if (random.stream().anyMatch(choice -> AgeFeatureSelection.tendrils(choice.id()))) found = true;
|
||||||
}
|
}
|
||||||
helper.assertTrue(found, "A page-free random Cave Age must be able to roll Tendrils");
|
helper.assertTrue(found, "A page-free random Cave Age must be able to roll Tendrils");
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ public final class ClientEvents {
|
|||||||
public static void registerRenderers(EntityRenderersEvent.RegisterRenderers event) {
|
public static void registerRenderers(EntityRenderersEvent.RegisterRenderers event) {
|
||||||
event.registerBlockEntityRenderer(ModBlockEntities.BOOK_STAND.get(), BookStandRenderer::new);
|
event.registerBlockEntityRenderer(ModBlockEntities.BOOK_STAND.get(), BookStandRenderer::new);
|
||||||
event.registerBlockEntityRenderer(ModBlockEntities.WRITING_DESK.get(), WritingDeskRenderer::new);
|
event.registerBlockEntityRenderer(ModBlockEntities.WRITING_DESK.get(), WritingDeskRenderer::new);
|
||||||
|
event.registerBlockEntityRenderer(ModBlockEntities.STAR_FISSURE.get(), StarFissureRenderer::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package net.dnorberg.rivencraft.client;
|
||||||
|
|
||||||
|
import net.dnorberg.rivencraft.block.entity.StarFissureBlockEntity;
|
||||||
|
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
|
||||||
|
import net.minecraft.client.renderer.blockentity.TheEndPortalRenderer;
|
||||||
|
|
||||||
|
/** Uses the modern end-portal starfield shader at Legacy's full-block face positions. */
|
||||||
|
public final class StarFissureRenderer extends TheEndPortalRenderer<StarFissureBlockEntity> {
|
||||||
|
public StarFissureRenderer(BlockEntityRendererProvider.Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected float getOffsetUp() {
|
||||||
|
return 0.0F;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected float getOffsetDown() {
|
||||||
|
return 1.0F;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import net.dnorberg.rivencraft.block.entity.BookStandBlockEntity;
|
|||||||
import net.dnorberg.rivencraft.block.entity.BookBinderBlockEntity;
|
import net.dnorberg.rivencraft.block.entity.BookBinderBlockEntity;
|
||||||
import net.dnorberg.rivencraft.block.entity.WritingDeskBlockEntity;
|
import net.dnorberg.rivencraft.block.entity.WritingDeskBlockEntity;
|
||||||
import net.dnorberg.rivencraft.block.entity.InkMixerBlockEntity;
|
import net.dnorberg.rivencraft.block.entity.InkMixerBlockEntity;
|
||||||
|
import net.dnorberg.rivencraft.block.entity.StarFissureBlockEntity;
|
||||||
import net.minecraft.core.registries.Registries;
|
import net.minecraft.core.registries.Registries;
|
||||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||||
import net.neoforged.bus.api.IEventBus;
|
import net.neoforged.bus.api.IEventBus;
|
||||||
@@ -28,6 +29,9 @@ public final class ModBlockEntities {
|
|||||||
public static final DeferredHolder<BlockEntityType<?>, BlockEntityType<InkMixerBlockEntity>> INK_MIXER =
|
public static final DeferredHolder<BlockEntityType<?>, BlockEntityType<InkMixerBlockEntity>> INK_MIXER =
|
||||||
BLOCK_ENTITIES.register("ink_mixer", () -> BlockEntityType.Builder
|
BLOCK_ENTITIES.register("ink_mixer", () -> BlockEntityType.Builder
|
||||||
.of(InkMixerBlockEntity::new, ModBlocks.INK_MIXER.get()).build(null));
|
.of(InkMixerBlockEntity::new, ModBlocks.INK_MIXER.get()).build(null));
|
||||||
|
public static final DeferredHolder<BlockEntityType<?>, BlockEntityType<StarFissureBlockEntity>> STAR_FISSURE =
|
||||||
|
BLOCK_ENTITIES.register("star_fissure", () -> BlockEntityType.Builder
|
||||||
|
.of(StarFissureBlockEntity::new, ModBlocks.STAR_FISSURE.get()).build(null));
|
||||||
|
|
||||||
private ModBlockEntities() {
|
private ModBlockEntities() {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import net.dnorberg.rivencraft.block.BookBinderBlock;
|
|||||||
import net.dnorberg.rivencraft.block.WritingDeskBlock;
|
import net.dnorberg.rivencraft.block.WritingDeskBlock;
|
||||||
import net.dnorberg.rivencraft.block.InkMixerBlock;
|
import net.dnorberg.rivencraft.block.InkMixerBlock;
|
||||||
import net.dnorberg.rivencraft.block.BuddingCrystalBlock;
|
import net.dnorberg.rivencraft.block.BuddingCrystalBlock;
|
||||||
|
import net.dnorberg.rivencraft.block.StarFissureBlock;
|
||||||
import net.minecraft.world.level.block.AmethystClusterBlock;
|
import net.minecraft.world.level.block.AmethystClusterBlock;
|
||||||
import net.minecraft.world.level.block.Block;
|
import net.minecraft.world.level.block.Block;
|
||||||
import net.minecraft.world.level.block.Blocks;
|
import net.minecraft.world.level.block.Blocks;
|
||||||
@@ -52,6 +53,9 @@ public final class ModBlocks {
|
|||||||
public static final DeferredBlock<AmethystClusterBlock> SMALL_CRYSTAL_BUD = BLOCKS.registerBlock("small_crystal_bud",
|
public static final DeferredBlock<AmethystClusterBlock> SMALL_CRYSTAL_BUD = BLOCKS.registerBlock("small_crystal_bud",
|
||||||
properties -> new AmethystClusterBlock(3.0F, 4.0F, properties),
|
properties -> new AmethystClusterBlock(3.0F, 4.0F, properties),
|
||||||
BlockBehaviour.Properties.ofFullCopy(Blocks.SMALL_AMETHYST_BUD));
|
BlockBehaviour.Properties.ofFullCopy(Blocks.SMALL_AMETHYST_BUD));
|
||||||
|
public static final DeferredBlock<StarFissureBlock> STAR_FISSURE = BLOCKS.registerBlock("star_fissure",
|
||||||
|
StarFissureBlock::new, BlockBehaviour.Properties.ofFullCopy(Blocks.END_PORTAL)
|
||||||
|
.lightLevel(state -> 6));
|
||||||
|
|
||||||
private ModBlocks() {
|
private ModBlocks() {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ public final class ModSounds {
|
|||||||
DeferredRegister.create(Registries.SOUND_EVENT, Rivencraft.MOD_ID);
|
DeferredRegister.create(Registries.SOUND_EVENT, Rivencraft.MOD_ID);
|
||||||
|
|
||||||
public static final DeferredHolder<SoundEvent, SoundEvent> LINK = register("linking.link");
|
public static final DeferredHolder<SoundEvent, SoundEvent> LINK = register("linking.link");
|
||||||
|
public static final DeferredHolder<SoundEvent, SoundEvent> LINK_FISSURE = register("linking.link-fissure");
|
||||||
|
|
||||||
private ModSounds() {
|
private ModSounds() {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package net.dnorberg.rivencraft.world;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
import net.dnorberg.rivencraft.registry.ModBlocks;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.world.level.block.Blocks;
|
||||||
|
import net.minecraft.world.level.levelgen.Heightmap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ports the footprint and carving of Mystcraft's WorldGenMystStarFissure.
|
||||||
|
* The original is LGPL-3.0, copyright XCompWiz and Mystcraft contributors.
|
||||||
|
*/
|
||||||
|
public final class StarFissureFeature {
|
||||||
|
private static final long SALT = 0x5354415246495353L;
|
||||||
|
|
||||||
|
private StarFissureFeature() {}
|
||||||
|
|
||||||
|
/** Legacy chooses a point 8–23 blocks inside/after the spawn chunk, then grows the rift along +Z. */
|
||||||
|
public static List<BlockPos> footprint(long ageSeed, BlockPos spawn) {
|
||||||
|
long chunkX = spawn.getX() >> 4;
|
||||||
|
long chunkZ = spawn.getZ() >> 4;
|
||||||
|
Random random = new Random(ageSeed ^ SALT ^ chunkX ^ (chunkZ << 32));
|
||||||
|
int originX = ((int) chunkX << 4) + random.nextInt(16) + 8;
|
||||||
|
int originZ = ((int) chunkZ << 4) + random.nextInt(16) + 8;
|
||||||
|
int length = random.nextInt(8) + 10;
|
||||||
|
int[][] intervals = new int[length][2];
|
||||||
|
List<BlockPos> columns = new ArrayList<>();
|
||||||
|
addRow(columns, originX, originZ, 0, 0, 0);
|
||||||
|
for (int row = 1; row < length; row++) {
|
||||||
|
int range = length + Math.min(row, length - row) + 1;
|
||||||
|
int right = (random.nextInt(range) - (range >> 1) + intervals[row - 1][1]) / 2;
|
||||||
|
int left = (random.nextInt(range) - (range >> 1) + intervals[row - 1][0]) / 2;
|
||||||
|
if (left > right) {
|
||||||
|
int swap = left;
|
||||||
|
left = right;
|
||||||
|
right = swap;
|
||||||
|
}
|
||||||
|
left = Math.min(left, intervals[row - 1][1]);
|
||||||
|
right = Math.max(right, intervals[row - 1][0]);
|
||||||
|
intervals[row][0] = left;
|
||||||
|
intervals[row][1] = right;
|
||||||
|
addRow(columns, originX, originZ, row, left, right);
|
||||||
|
}
|
||||||
|
return List.copyOf(columns);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addRow(List<BlockPos> columns, int originX, int originZ, int row, int left, int right) {
|
||||||
|
for (int x = left; x <= right; x++) {
|
||||||
|
columns.add(new BlockPos(originX + x, 0, originZ + row * 2));
|
||||||
|
columns.add(new BlockPos(originX + x, 0, originZ + row * 2 + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void generate(ServerLevel level, long ageSeed, BlockPos spawn) {
|
||||||
|
List<BlockPos> columns = footprint(ageSeed, spawn);
|
||||||
|
BlockPos first = columns.getFirst();
|
||||||
|
// 1.12 worlds began at y=0. Modern superflat can sit below it, so use the
|
||||||
|
// modern world floor there. Empty Void columns keep Legacy's y=0 portal.
|
||||||
|
int surface = level.getHeight(Heightmap.Types.WORLD_SURFACE, first.getX(), first.getZ());
|
||||||
|
int minY = level.getMinBuildHeight();
|
||||||
|
int floorY = surface > minY && surface <= 0 ? minY + 1 : Math.max(0, minY + 1);
|
||||||
|
BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos();
|
||||||
|
for (BlockPos column : columns) {
|
||||||
|
int top = level.getHeight(Heightmap.Types.WORLD_SURFACE, column.getX(), column.getZ());
|
||||||
|
level.setBlock(mutable.set(column.getX(), floorY, column.getZ()),
|
||||||
|
ModBlocks.STAR_FISSURE.get().defaultBlockState(), 2);
|
||||||
|
// Legacy clears all blocks above each portal tile to the sky. Heightmap
|
||||||
|
// bounds the same work without iterating already-empty world height.
|
||||||
|
for (int y = floorY + 1; y < top; y++) {
|
||||||
|
level.setBlock(mutable.set(column.getX(), y, column.getZ()), Blocks.AIR.defaultBlockState(), 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package net.dnorberg.rivencraft.world;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import net.dnorberg.rivencraft.Rivencraft;
|
||||||
|
import net.dnorberg.rivencraft.book.AgeFeatureSelection;
|
||||||
|
import net.dnorberg.rivencraft.book.AgeGenerationPlan;
|
||||||
|
import net.dnorberg.rivencraft.book.AgeGenerationProfiles;
|
||||||
|
import net.dnorberg.rivencraft.book.AgeRuntimeProfiles;
|
||||||
|
import net.dnorberg.rivencraft.book.AgeTraits;
|
||||||
|
import net.dnorberg.rivencraft.item.PageItem;
|
||||||
|
import net.dnorberg.rivencraft.registry.ModBlocks;
|
||||||
|
import net.dnorberg.rivencraft.writing.OrderedWriting;
|
||||||
|
import net.dnorberg.rivencraft.writing.SymbolReference;
|
||||||
|
import net.dnorberg.rivencraft.writing.WritingEntry;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.gametest.framework.GameTest;
|
||||||
|
import net.minecraft.gametest.framework.GameTestHelper;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.neoforged.neoforge.gametest.GameTestHolder;
|
||||||
|
import net.neoforged.neoforge.gametest.PrefixGameTestTemplate;
|
||||||
|
|
||||||
|
/** Pure selection and footprint checks; runtime carving is left to the in-game visual check. */
|
||||||
|
@GameTestHolder(Rivencraft.MOD_ID)
|
||||||
|
@PrefixGameTestTemplate(false)
|
||||||
|
public final class StarFissureGameTests {
|
||||||
|
private StarFissureGameTests() {}
|
||||||
|
|
||||||
|
@GameTest(template = "empty")
|
||||||
|
public static void authoredFissureIsWritableAndSaved(GameTestHelper helper) {
|
||||||
|
var registries = helper.getLevel().registryAccess();
|
||||||
|
helper.assertTrue(PageItem.bundledCreativeSymbols().contains("feature/star_fissures"),
|
||||||
|
"Star Fissures must appear in the creative and JEI page feed");
|
||||||
|
helper.assertTrue(PageItem.isUsableWriting(PageItem.createBundledCreativePage("feature/star_fissures")),
|
||||||
|
"The page must be usable in an authored book");
|
||||||
|
OrderedWriting writing = new OrderedWriting(List.of(WritingEntry.linkPanel(),
|
||||||
|
page("terrain/standard"), page("feature/star_fissures")));
|
||||||
|
AgeGenerationPlan plan = AgeGenerationPlan.compile(writing, 58L, AgeGenerationProfiles.STANDARD,
|
||||||
|
AgeTraits.select(58L), registries);
|
||||||
|
var resolved = AgeFeatureSelection.resolved(plan);
|
||||||
|
helper.assertValueEqual(resolved.size(), 1, "One authored fissure must remain one feature choice");
|
||||||
|
helper.assertValueEqual(resolved.getFirst().id(), AgeFeatureSelection.STAR_FISSURES,
|
||||||
|
"The authored feature must be a Star Fissure");
|
||||||
|
helper.assertTrue(resolved.getFirst().material().isEmpty(), "Star Fissures consume no material page");
|
||||||
|
helper.assertValueEqual(AgeFeatureSelection.resolved(AgeGenerationPlan.load(plan.save())), resolved,
|
||||||
|
"The feature must survive Age-plan save and load");
|
||||||
|
helper.assertTrue(ModBlocks.STAR_FISSURE.get().defaultBlockState() != null,
|
||||||
|
"The non-obtainable star portal block must be registered");
|
||||||
|
for (String terrain : List.of("standard", "amplified", "island", "flat", "cave", "void"))
|
||||||
|
helper.assertTrue(AgeFeatureSelection.error(AgeFeatureSelection.STAR_FISSURES,
|
||||||
|
AgeRuntimeProfiles.resolve(id(terrain)), registries) == null,
|
||||||
|
"Legacy fissures can appear on " + terrain + " terrain");
|
||||||
|
helper.succeed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GameTest(template = "empty")
|
||||||
|
public static void randomFissuresAndSpawnFootprintAreStable(GameTestHelper helper) {
|
||||||
|
var registries = helper.getLevel().registryAccess();
|
||||||
|
int selected = 0;
|
||||||
|
for (long seed = 0; seed < 128; seed++) {
|
||||||
|
boolean standard = hasRandomFissure(seed, AgeGenerationProfiles.STANDARD, registries);
|
||||||
|
if (standard) selected++;
|
||||||
|
for (String terrain : List.of("cave", "void"))
|
||||||
|
helper.assertValueEqual(hasRandomFissure(seed, id(terrain), registries), standard,
|
||||||
|
"Legacy small-feature selection must not depend on terrain");
|
||||||
|
}
|
||||||
|
helper.assertTrue(selected > 0 && selected < 128,
|
||||||
|
"The weighted Legacy grammar should select fissures in some, not all, random Ages");
|
||||||
|
BlockPos spawn = new BlockPos(0, 64, 0);
|
||||||
|
var footprint = StarFissureFeature.footprint(58L, spawn);
|
||||||
|
helper.assertValueEqual(footprint, StarFissureFeature.footprint(58L, spawn),
|
||||||
|
"The Age seed and spawn must reproduce the same fissure");
|
||||||
|
int minZ = footprint.stream().mapToInt(BlockPos::getZ).min().orElseThrow();
|
||||||
|
int maxZ = footprint.stream().mapToInt(BlockPos::getZ).max().orElseThrow();
|
||||||
|
helper.assertTrue(minZ >= 8 && minZ <= 23,
|
||||||
|
"Legacy starts 8–23 blocks past the spawn chunk's Z edge");
|
||||||
|
helper.assertTrue(maxZ - minZ + 1 >= 20 && maxZ - minZ + 1 <= 34,
|
||||||
|
"Legacy's rift spans 10–17 paired Z rows");
|
||||||
|
for (int z = minZ; z <= maxZ; z++) {
|
||||||
|
int row = z;
|
||||||
|
var xs = footprint.stream().filter(pos -> pos.getZ() == row)
|
||||||
|
.mapToInt(BlockPos::getX).sorted().toArray();
|
||||||
|
helper.assertTrue(xs.length > 0, "Every Z row must contain portal tiles");
|
||||||
|
for (int i = 1; i < xs.length; i++)
|
||||||
|
helper.assertValueEqual(xs[i], xs[i - 1] + 1,
|
||||||
|
"Each jagged Legacy row remains contiguous");
|
||||||
|
}
|
||||||
|
helper.succeed();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ResourceLocation id(String path) {
|
||||||
|
return ResourceLocation.fromNamespaceAndPath(Rivencraft.MOD_ID, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hasRandomFissure(long seed, ResourceLocation terrain,
|
||||||
|
net.minecraft.core.RegistryAccess registries) {
|
||||||
|
return AgeFeatureSelection.randomChoices(seed, AgeRuntimeProfiles.resolve(terrain), registries).stream()
|
||||||
|
.anyMatch(choice -> choice.id().equals(AgeFeatureSelection.STAR_FISSURES));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static WritingEntry page(String path) {
|
||||||
|
return WritingEntry.symbol(new SymbolReference(id(path)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"variants":{"":{"model":"rivencraft:block/star_fissure"}}}
|
||||||
@@ -289,6 +289,9 @@
|
|||||||
"symbol.rivencraft.feature.obelisks.description": "Add rare tall obelisks on exposed solid ground, with a random or selected block material.",
|
"symbol.rivencraft.feature.obelisks.description": "Add rare tall obelisks on exposed solid ground, with a random or selected block material.",
|
||||||
"symbol.rivencraft.feature.crystal_formations": "Crystal Formations",
|
"symbol.rivencraft.feature.crystal_formations": "Crystal Formations",
|
||||||
"symbol.rivencraft.feature.crystal_formations.description": "Add blue crystal formations whose budding blocks grow harvestable clusters.",
|
"symbol.rivencraft.feature.crystal_formations.description": "Add blue crystal formations whose budding blocks grow harvestable clusters.",
|
||||||
|
"symbol.rivencraft.feature.star_fissures": "Star Fissures",
|
||||||
|
"symbol.rivencraft.feature.star_fissures.description": "Open a starry fissure near this Age's spawn that links back to the Overworld.",
|
||||||
|
"block.rivencraft.star_fissure": "Star Fissure",
|
||||||
"block.rivencraft.crystal_block": "Crystal Block",
|
"block.rivencraft.crystal_block": "Crystal Block",
|
||||||
"block.rivencraft.budding_crystal": "Budding Crystal",
|
"block.rivencraft.budding_crystal": "Budding Crystal",
|
||||||
"block.rivencraft.small_crystal_bud": "Small Crystal Bud",
|
"block.rivencraft.small_crystal_bud": "Small Crystal Bud",
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
{"parent":"minecraft:block/end_portal"}
|
||||||
@@ -6,5 +6,9 @@
|
|||||||
"name": "rivencraft:linking/link"
|
"name": "rivencraft:linking/link"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"linking.link-fissure": {
|
||||||
|
"category": "player",
|
||||||
|
"sounds": [{"name": "rivencraft:linking/link-fissure"}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"schema_version": 2,
|
||||||
|
"name": "symbol.rivencraft.feature.star_fissures",
|
||||||
|
"description": "symbol.rivencraft.feature.star_fissures.description",
|
||||||
|
"category": "rivencraft:feature",
|
||||||
|
"semantics": { "role": "feature", "value": "rivencraft:star_fissures" },
|
||||||
|
"poem": "Star Fissure World Home"
|
||||||
|
}
|
||||||
Reference in new issue
Block a user