Public Access
This commit is contained in:
1 parent
28c2ca0db3
commit
e28079566b
21 files changed
+398
-78
No files matched your search
@@ -22,7 +22,7 @@ public record AgeDefinition(
|
||||
public static final ResourceLocation RANDOM_VANILLA_PROFILE = ResourceLocation.fromNamespaceAndPath(
|
||||
Rivencraft.MOD_ID, "random_vanilla"
|
||||
);
|
||||
public static final int CURRENT_GENERATION_VERSION = 14;
|
||||
public static final int CURRENT_GENERATION_VERSION = 15;
|
||||
|
||||
public AgeDefinition {
|
||||
Objects.requireNonNull(id, "id");
|
||||
|
||||
@@ -60,6 +60,7 @@ public final class AgeDimensionService {
|
||||
DynamicDimensionLoadCallback.register((server, loader) -> {
|
||||
ServerLevel overworld = server.overworld();
|
||||
for (AgeDefinition age : AgeRegistrySavedData.get(overworld).definitions()) {
|
||||
if (!canLoadProfile(server, age)) continue;
|
||||
ServerLevel level = loader.loadDynamicDimension(
|
||||
age.dimensionId(),
|
||||
createGenerator(server, age),
|
||||
@@ -106,6 +107,8 @@ public final class AgeDimensionService {
|
||||
return existing;
|
||||
}
|
||||
|
||||
if (!canLoadProfile(server, definition)) return null;
|
||||
|
||||
ServerLevel overworld = server.overworld();
|
||||
ServerLevel loaded = DynamicDimensionRegistry.from(server).loadDynamicDimension(
|
||||
definition.dimensionId(),
|
||||
@@ -154,6 +157,22 @@ public final class AgeDimensionService {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean canLoadProfile(MinecraftServer server, AgeDefinition age) {
|
||||
try {
|
||||
AgeRuntimeProfiles.validateReferences(server.registryAccess(), age.generationProfile(), runtimeProfile(age));
|
||||
return true;
|
||||
} catch (IllegalArgumentException exception) {
|
||||
Rivencraft.LOGGER.error("Cannot load Age {} ({}): {}. Restore its data pack; saved Age data is unchanged.",
|
||||
age.id(), age.dimensionId(), exception.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static AgeRuntimeProfile runtimeProfile(AgeDefinition age) {
|
||||
return age.plan() == null ? AgeRuntimeProfiles.resolve(age.generationProfile())
|
||||
: age.plan().runtimeProfile();
|
||||
}
|
||||
|
||||
private static void applyInitialCelestialPhase(ServerLevel level, AgeDefinition age) {
|
||||
long phaseTime = (long)(age.traits().celestialPhase() * Level.TICKS_PER_DAY);
|
||||
level.setDayTime(phaseTime);
|
||||
@@ -267,8 +286,7 @@ public final class AgeDimensionService {
|
||||
}
|
||||
|
||||
private static ChunkGenerator createGenerator(MinecraftServer server, AgeDefinition age) {
|
||||
AgeRuntimeProfile profile = age.plan() == null ? AgeRuntimeProfiles.resolve(age.generationProfile())
|
||||
: age.plan().runtimeProfile();
|
||||
AgeRuntimeProfile profile = runtimeProfile(age);
|
||||
if (profile.terrain() == AgeRuntimeProfile.Terrain.FLAT) {
|
||||
return new FlatLevelSource(FlatLevelGeneratorSettings.getDefault(
|
||||
server.registryAccess().lookupOrThrow(Registries.BIOME),
|
||||
@@ -392,12 +410,11 @@ public final class AgeDimensionService {
|
||||
}
|
||||
|
||||
private static DimensionType createDimensionType(MinecraftServer server, AgeDefinition age) {
|
||||
AgeRuntimeProfile profile = age.plan() == null ? AgeRuntimeProfiles.resolve(age.generationProfile())
|
||||
: age.plan().runtimeProfile();
|
||||
AgeRuntimeProfile profile = runtimeProfile(age);
|
||||
DimensionType base = server.registryAccess().lookupOrThrow(Registries.DIMENSION_TYPE)
|
||||
.getOrThrow(ResourceKey.create(Registries.DIMENSION_TYPE, profile.dimensionType())).value();
|
||||
AgeTraits.Lighting lighting = age.traits().lighting();
|
||||
boolean hasSkyLight = lighting != AgeTraits.Lighting.DARK;
|
||||
boolean hasSkyLight = base.hasSkyLight() && lighting != AgeTraits.Lighting.DARK;
|
||||
float ambientLight = lighting == AgeTraits.Lighting.BRIGHT ? 0.25F : 0.0F;
|
||||
return new DimensionType(
|
||||
base.fixedTime(),
|
||||
|
||||
@@ -70,14 +70,19 @@ public final class AgeRuntimeProfiles {
|
||||
ResourceLocation noise = location(object, "noise_settings", "minecraft:overworld");
|
||||
ResourceLocation biome = location(object, "biome_preset", "minecraft:overworld");
|
||||
ResourceLocation dimension = location(object, "dimension_type", "minecraft:overworld");
|
||||
AgeRuntimeProfile profile = new AgeRuntimeProfile(schemaVersion, terrain, noise, biome, dimension);
|
||||
validateReferences(registries, id, profile);
|
||||
return profile;
|
||||
}
|
||||
|
||||
if (terrain == AgeRuntimeProfile.Terrain.NOISE) {
|
||||
requireRegistryEntry(registries, Registries.NOISE_SETTINGS, noise, id, "noise_settings");
|
||||
/** Recheck frozen plans before loading: a removed registry entry must not crash or silently alter an Age. */
|
||||
public static void validateReferences(RegistryAccess registries, ResourceLocation id, AgeRuntimeProfile profile) {
|
||||
if (profile.terrain() == AgeRuntimeProfile.Terrain.NOISE) {
|
||||
requireRegistryEntry(registries, Registries.NOISE_SETTINGS, profile.noiseSettings(), id, "noise_settings");
|
||||
requireRegistryEntry(registries, Registries.MULTI_NOISE_BIOME_SOURCE_PARAMETER_LIST,
|
||||
biome, id, "biome_preset");
|
||||
profile.biomePreset(), id, "biome_preset");
|
||||
}
|
||||
requireRegistryEntry(registries, Registries.DIMENSION_TYPE, dimension, id, "dimension_type");
|
||||
return new AgeRuntimeProfile(schemaVersion, terrain, noise, biome, dimension);
|
||||
requireRegistryEntry(registries, Registries.DIMENSION_TYPE, profile.dimensionType(), id, "dimension_type");
|
||||
}
|
||||
|
||||
private static String requiredString(JsonObject object, String name) {
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
package net.dnorberg.rivencraft.book;
|
||||
|
||||
import com.google.gson.JsonParser;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import net.dnorberg.rivencraft.writing.OrderedWriting;
|
||||
import net.dnorberg.rivencraft.writing.SymbolCatalog;
|
||||
import net.dnorberg.rivencraft.writing.SymbolReference;
|
||||
import net.dnorberg.rivencraft.writing.SymbolSemantics;
|
||||
import net.dnorberg.rivencraft.writing.WritingEntry;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.gametest.framework.GameTest;
|
||||
import net.minecraft.gametest.framework.GameTestHelper;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.dimension.BuiltinDimensionTypes;
|
||||
import net.minecraft.world.level.levelgen.FlatLevelSource;
|
||||
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
|
||||
import net.neoforged.neoforge.gametest.GameTestHolder;
|
||||
import net.neoforged.neoforge.gametest.PrefixGameTestTemplate;
|
||||
|
||||
/** Issue #32: authored terrain must drive actual runtime generation and survive reload. */
|
||||
@GameTestHolder("rivencraft_archive_linking")
|
||||
@PrefixGameTestTemplate(false)
|
||||
public final class TerrainWritingGameTests {
|
||||
private TerrainWritingGameTests() {}
|
||||
|
||||
@GameTest(template = "empty", timeoutTicks = 600)
|
||||
public static void writtenTerrainFamiliesGenerateSafelyAndReload(GameTestHelper helper) {
|
||||
var player = helper.makeMockServerPlayerInLevel();
|
||||
checkWrittenTerrain(helper, player, AgeGenerationProfiles.all(), 0);
|
||||
}
|
||||
|
||||
private static void checkWrittenTerrain(GameTestHelper helper,
|
||||
net.minecraft.server.level.ServerPlayer player,
|
||||
List<ResourceLocation> profiles, int index) {
|
||||
if (index >= profiles.size()) {
|
||||
helper.succeed();
|
||||
return;
|
||||
}
|
||||
var server = helper.getLevel().getServer();
|
||||
ResourceLocation profileId = profiles.get(index);
|
||||
SymbolReference symbol = new SymbolReference(ResourceLocation.fromNamespaceAndPath(
|
||||
"rivencraft", "terrain/" + profileId.getPath()));
|
||||
var semantics = SymbolCatalog.find(symbol).orElseThrow().semantics().orElseThrow();
|
||||
helper.assertValueEqual(semantics.role(), SymbolSemantics.Role.TERRAIN,
|
||||
"A built-in terrain page must have terrain semantics");
|
||||
helper.assertValueEqual(((SymbolSemantics.ResourceValue) semantics.value()).id(), profileId,
|
||||
"A terrain page must point at its matching profile");
|
||||
|
||||
OrderedWriting writing = new OrderedWriting(List.of(
|
||||
WritingEntry.linkPanel(), WritingEntry.symbol(symbol)));
|
||||
AgeReference reference = new AgeReference(UUID.randomUUID(),
|
||||
"Written " + profileId.getPath() + " " + UUID.randomUUID());
|
||||
var target = AgeDimensionService.resolve(player, reference, writing).orElseThrow();
|
||||
AgeDefinition age = AgeRegistrySavedData.get(helper.getLevel()).find(reference.id()).orElseThrow();
|
||||
helper.assertValueEqual(age.generationProfile(), profileId,
|
||||
"Written terrain must override the random profile chosen from the seed");
|
||||
helper.assertValueEqual(age.plan().runtimeProfile(), AgeRuntimeProfiles.profiles().get(profileId),
|
||||
"The selected noise and dimension-type resources must be frozen in the plan");
|
||||
helper.runAfterDelay(1, () -> {
|
||||
var level = server.getLevel(target.location().dimension());
|
||||
helper.assertTrue(level != null, "The written Age must create a runtime dimension");
|
||||
assertGenerator(helper, level, age.plan().runtimeProfile());
|
||||
BlockPos spawn = target.location().pos();
|
||||
helper.assertTrue(level.getBlockState(spawn.below()).is(Blocks.COBBLESTONE)
|
||||
&& level.getBlockState(spawn).isAir()
|
||||
&& level.getBlockState(spawn.above()).isAir(),
|
||||
"Every written terrain family must retain a safe arrival platform");
|
||||
|
||||
helper.assertValueEqual(AgeDimensionService.unload(server, age),
|
||||
AgeDimensionService.UnloadResult.UNLOADED,
|
||||
"A written Age must unload without losing its plan");
|
||||
var reloaded = AgeDimensionService.ensureLoaded(server, age);
|
||||
helper.assertTrue(reloaded != null, "The written Age must reload from its frozen plan");
|
||||
assertGenerator(helper, reloaded, age.plan().runtimeProfile());
|
||||
helper.assertValueEqual(reloaded.dimensionType().effectsLocation(),
|
||||
level.dimensionType().effectsLocation(), "Reload must retain dimension effects");
|
||||
helper.runAfterDelay(1, () -> checkWrittenTerrain(helper, player, profiles, index + 1));
|
||||
});
|
||||
}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void missingRegistryResourcesRejectProfileAndPreserveAge(GameTestHelper helper) {
|
||||
ResourceLocation missing = ResourceLocation.parse("rivencraft:missing_noise");
|
||||
var json = JsonParser.parseString("""
|
||||
{"schema_version":1,"terrain":"noise","noise_settings":"rivencraft:missing_noise",
|
||||
"biome_preset":"minecraft:overworld","dimension_type":"minecraft:overworld"}
|
||||
""");
|
||||
boolean rejected = false;
|
||||
try {
|
||||
AgeRuntimeProfiles.parse(missing, json, helper.getLevel().registryAccess());
|
||||
} catch (IllegalArgumentException exception) {
|
||||
rejected = exception.getMessage().contains("noise_settings")
|
||||
&& exception.getMessage().contains(missing.toString());
|
||||
}
|
||||
helper.assertTrue(rejected, "A profile with missing noise settings needs an actionable rejection");
|
||||
|
||||
long seed = 42L;
|
||||
OrderedWriting writing = new OrderedWriting(List.of(WritingEntry.linkPanel(),
|
||||
WritingEntry.symbol(new SymbolReference(ResourceLocation.parse("rivencraft:terrain/standard")))));
|
||||
AgeRuntimeProfile broken = new AgeRuntimeProfile(1, AgeRuntimeProfile.Terrain.NOISE,
|
||||
missing, ResourceLocation.parse("minecraft:overworld"),
|
||||
ResourceLocation.parse("minecraft:overworld"));
|
||||
AgeGenerationPlan plan = new AgeGenerationPlan(1, seed, writing,
|
||||
List.of(new AgeGenerationPlan.ResolvedSymbol(1,
|
||||
writing.entries().get(1).symbol().orElseThrow(),
|
||||
new SymbolSemantics(SymbolSemantics.Role.TERRAIN,
|
||||
new SymbolSemantics.ResourceValue(AgeGenerationProfiles.STANDARD)))), broken);
|
||||
AgeDefinition age = new AgeDefinition(UUID.randomUUID(), "Missing Resource", seed,
|
||||
AgeGenerationProfiles.STANDARD, AgeDefinition.CURRENT_GENERATION_VERSION,
|
||||
helper.getLevel().getGameTime(),
|
||||
ResourceLocation.parse("rivencraft:gametest/missing_resource_" + UUID.randomUUID()),
|
||||
null, AgeTraits.select(seed), plan);
|
||||
helper.assertTrue(AgeDimensionService.ensureLoaded(helper.getLevel().getServer(), age) == null,
|
||||
"A removed resource must not silently turn an authored Age into a different terrain");
|
||||
helper.assertValueEqual(age.plan(), plan, "Failed loading must not mutate the saved plan");
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void dimensionTypeSnapshotKeepsNetherSkylightRules(GameTestHelper helper) {
|
||||
var profile = AgeRuntimeProfiles.parse(ResourceLocation.parse("rivencraft:gametest_nether"),
|
||||
JsonParser.parseString("""
|
||||
{"schema_version":1,"terrain":"noise","noise_settings":"minecraft:nether",
|
||||
"biome_preset":"minecraft:nether","dimension_type":"minecraft:the_nether"}
|
||||
"""), helper.getLevel().registryAccess());
|
||||
long seed = 99L;
|
||||
OrderedWriting writing = new OrderedWriting(List.of(WritingEntry.linkPanel(),
|
||||
WritingEntry.symbol(new SymbolReference(ResourceLocation.parse("rivencraft:feature/caves")))));
|
||||
var parsed = net.dnorberg.rivencraft.writing.WritingGrammar.parse(writing);
|
||||
AgeGenerationPlan plan = new AgeGenerationPlan(1, seed, writing,
|
||||
parsed.symbols().stream().map(symbol -> new AgeGenerationPlan.ResolvedSymbol(
|
||||
symbol.index(), symbol.reference(), symbol.semantics())).toList(), profile);
|
||||
AgeDefinition age = new AgeDefinition(UUID.randomUUID(), "Nether Type Snapshot", seed,
|
||||
ResourceLocation.parse("rivencraft:gametest_nether"), AgeDefinition.CURRENT_GENERATION_VERSION,
|
||||
helper.getLevel().getGameTime(),
|
||||
ResourceLocation.parse("rivencraft:gametest/nether_type_" + UUID.randomUUID()),
|
||||
null, AgeTraits.LEGACY_DEFAULTS, plan);
|
||||
var level = AgeDimensionService.ensureLoaded(helper.getLevel().getServer(), age);
|
||||
helper.assertTrue(level != null, "A valid custom dimension-type snapshot must load");
|
||||
helper.assertFalse(level.dimensionType().hasSkyLight(),
|
||||
"Normal lighting must not add skylight to a Nether-style dimension type");
|
||||
helper.assertValueEqual(level.dimensionType().effectsLocation(), BuiltinDimensionTypes.NETHER_EFFECTS,
|
||||
"The authored profile's dimension effects must reach the runtime Age");
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
private static void assertGenerator(GameTestHelper helper, net.minecraft.server.level.ServerLevel level,
|
||||
AgeRuntimeProfile profile) {
|
||||
var generator = level.getChunkSource().getGenerator();
|
||||
if (profile.terrain() == AgeRuntimeProfile.Terrain.NOISE) {
|
||||
helper.assertTrue(generator instanceof NoiseBasedChunkGenerator,
|
||||
"Noise terrain must use a noise-based chunk generator");
|
||||
var expected = level.registryAccess().lookupOrThrow(Registries.NOISE_SETTINGS).getOrThrow(
|
||||
ResourceKey.create(Registries.NOISE_SETTINGS, profile.noiseSettings())).value();
|
||||
helper.assertValueEqual(((NoiseBasedChunkGenerator) generator).generatorSettings().value().noiseSettings(),
|
||||
expected.noiseSettings(), "Written noise settings must be applied to the generator");
|
||||
} else {
|
||||
helper.assertTrue(generator instanceof FlatLevelSource,
|
||||
"Flat and Void terrain must use a flat-source generator");
|
||||
if (profile.terrain() == AgeRuntimeProfile.Terrain.VOID) {
|
||||
helper.assertTrue(generator.getBiomeSource().possibleBiomes().stream()
|
||||
.allMatch(biome -> biome.is(net.minecraft.world.level.biome.Biomes.THE_VOID)),
|
||||
"Void terrain must use the Void biome");
|
||||
}
|
||||
}
|
||||
var expectedType = level.registryAccess().lookupOrThrow(Registries.DIMENSION_TYPE).getOrThrow(
|
||||
ResourceKey.create(Registries.DIMENSION_TYPE, profile.dimensionType())).value();
|
||||
helper.assertValueEqual(level.dimensionType().effectsLocation(), expectedType.effectsLocation(),
|
||||
"The profile's dimension-type effects must be applied");
|
||||
}
|
||||
}
|
||||
@@ -106,6 +106,22 @@ public final class WritingDataGameTests {
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void creativePagesIncludeEveryBuiltInTerrain(GameTestHelper helper) {
|
||||
for (String terrain : List.of("standard", "amplified", "cave", "island", "flat", "void")) {
|
||||
String path = "terrain/" + terrain;
|
||||
helper.assertTrue(PageItem.bundledCreativeSymbols().contains(path),
|
||||
"The Pages creative tab must include " + path);
|
||||
ItemStack page = PageItem.createBundledCreativePage(path);
|
||||
helper.assertValueEqual(PageItem.content(page).symbol().orElseThrow().id(),
|
||||
ResourceLocation.fromNamespaceAndPath("rivencraft", path),
|
||||
"The creative starter page must contain the matching terrain symbol");
|
||||
helper.assertTrue(page.get(ModDataComponents.PAGE_SYMBOL_DISPLAY.get()).resolved(),
|
||||
"The creative starter page must have display metadata before catalog reload");
|
||||
}
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void semanticSchemaIsServerOnlyBoundedAndV1Compatible(GameTestHelper helper) {
|
||||
var builtIn = SymbolCatalog.find(reference("rivencraft:terrain/standard")).orElseThrow();
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
package net.dnorberg.rivencraft.item;
|
||||
|
||||
import com.google.gson.JsonParser;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.dnorberg.rivencraft.registry.ModDataComponents;
|
||||
import net.dnorberg.rivencraft.registry.ModItems;
|
||||
import net.dnorberg.rivencraft.writing.PageContent;
|
||||
import net.dnorberg.rivencraft.writing.SymbolCatalog;
|
||||
import net.dnorberg.rivencraft.writing.SymbolDefinition;
|
||||
import net.dnorberg.rivencraft.writing.SymbolDisplayMetadata;
|
||||
import net.dnorberg.rivencraft.writing.SymbolReference;
|
||||
import net.dnorberg.rivencraft.writing.SymbolPoems;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.network.chat.Component;
|
||||
@@ -18,6 +24,8 @@ import net.minecraft.world.item.TooltipFlag;
|
||||
|
||||
/** One stackable Page item whose server-owned component identifies its writing state. */
|
||||
public final class PageItem extends Item {
|
||||
private static final Map<String, SymbolDisplayMetadata> CREATIVE_SYMBOLS = loadCreativeSymbols();
|
||||
|
||||
public PageItem(Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
@@ -29,23 +37,44 @@ public final class PageItem extends Item {
|
||||
return create(PageContent.symbol(reference));
|
||||
}
|
||||
|
||||
/** Fixed starter examples for the creative tab; the server still validates catalog membership on use. */
|
||||
public static ItemStack createCreativeStarterPage(String path) {
|
||||
if (!List.of("terrain/standard", "biome/native", "lighting/normal", "weather/normal",
|
||||
"celestial/normal_time", "feature/caves").contains(path)) {
|
||||
throw new IllegalArgumentException("Not a starter symbol: " + path);
|
||||
public static List<String> bundledCreativeSymbols() {
|
||||
return List.copyOf(CREATIVE_SYMBOLS.keySet());
|
||||
}
|
||||
|
||||
/** Bundled symbols for the creative tab; the server still validates catalog membership on use. */
|
||||
public static ItemStack createBundledCreativePage(String path) {
|
||||
SymbolDisplayMetadata metadata = CREATIVE_SYMBOLS.get(path);
|
||||
if (metadata == null) {
|
||||
throw new IllegalArgumentException("Not a bundled symbol: " + path);
|
||||
}
|
||||
SymbolReference reference = new SymbolReference(
|
||||
net.minecraft.resources.ResourceLocation.fromNamespaceAndPath(Rivencraft.MOD_ID, path));
|
||||
SymbolReference reference = new SymbolReference(metadata.id());
|
||||
ItemStack page = create(PageContent.symbol(reference));
|
||||
String key = "symbol.rivencraft." + path.replace('/', '.');
|
||||
page.set(ModDataComponents.PAGE_SYMBOL_DISPLAY.get(), new SymbolDisplayMetadata(
|
||||
reference.id(), key, key + ".description",
|
||||
net.minecraft.resources.ResourceLocation.fromNamespaceAndPath(Rivencraft.MOD_ID, path.split("/")[0]),
|
||||
true, SymbolPoems.builtIn(reference.id())));
|
||||
page.set(ModDataComponents.PAGE_SYMBOL_DISPLAY.get(), metadata);
|
||||
return page;
|
||||
}
|
||||
|
||||
private static Map<String, SymbolDisplayMetadata> loadCreativeSymbols() {
|
||||
String resource = "/assets/rivencraft/creative_symbol_pages.json";
|
||||
try (var stream = PageItem.class.getResourceAsStream(resource)) {
|
||||
if (stream == null) {
|
||||
throw new IllegalStateException("Missing generated creative symbol index " + resource);
|
||||
}
|
||||
var json = JsonParser.parseReader(new InputStreamReader(stream, StandardCharsets.UTF_8))
|
||||
.getAsJsonObject();
|
||||
Map<String, SymbolDisplayMetadata> symbols = new LinkedHashMap<>();
|
||||
json.entrySet().forEach(entry -> {
|
||||
var id = net.minecraft.resources.ResourceLocation.parse(entry.getKey());
|
||||
if (!id.getNamespace().equals(Rivencraft.MOD_ID)) {
|
||||
throw new IllegalStateException("Creative symbol is not a Rivencraft page: " + id);
|
||||
}
|
||||
symbols.put(id.getPath(), SymbolDefinition.parse(entry.getValue()).display(id));
|
||||
});
|
||||
return Collections.unmodifiableMap(symbols);
|
||||
} catch (java.io.IOException exception) {
|
||||
throw new IllegalStateException("Could not read generated creative symbol index " + resource, exception);
|
||||
}
|
||||
}
|
||||
|
||||
public static ItemStack createLinkPanelPage() {
|
||||
return create(PageContent.LINK_PANEL);
|
||||
}
|
||||
|
||||
@@ -35,9 +35,8 @@ public final class ModCreativeTabs {
|
||||
.displayItems((parameters, output) -> {
|
||||
output.accept(ModItems.PAGE.get());
|
||||
output.accept(PageItem.createLinkPanelPage());
|
||||
for (String id : new String[] {"terrain/standard", "biome/native", "lighting/normal",
|
||||
"weather/normal", "celestial/normal_time", "feature/caves"}) {
|
||||
output.accept(PageItem.createCreativeStarterPage(id));
|
||||
for (String id : PageItem.bundledCreativeSymbols()) {
|
||||
output.accept(PageItem.createBundledCreativePage(id));
|
||||
}
|
||||
})
|
||||
.build());
|
||||
|
||||
@@ -10,6 +10,11 @@ public final class SymbolPoems {
|
||||
if (!id.getNamespace().equals("rivencraft")) return "";
|
||||
return switch (id.getPath()) {
|
||||
case "terrain/standard" -> "Terrain Form Tradition Flow";
|
||||
case "terrain/amplified" -> "Terrain Form Tradition Spur";
|
||||
case "terrain/cave" -> "Terrain Transform Void Flow";
|
||||
case "terrain/island" -> "Terrain Transform Form Celestial";
|
||||
case "terrain/flat" -> "Terrain Form Inhibit Motion";
|
||||
case "terrain/void" -> "Terrain Form Infinite Void";
|
||||
case "biome/native" -> "Constraint Nature Tradition Sustain";
|
||||
case "lighting/normal" -> "Ethereal Dynamic Cycle Balance";
|
||||
case "weather/normal" -> "Sustain Dynamic Tradition Balance";
|
||||
|
||||
@@ -120,6 +120,16 @@
|
||||
"symbol.rivencraft.unresolved.description": "Missing symbol: %s",
|
||||
"symbol.rivencraft.terrain.standard": "Standard Terrain",
|
||||
"symbol.rivencraft.terrain.standard.description": "A familiar continental terrain foundation.",
|
||||
"symbol.rivencraft.terrain.amplified": "Amplified Terrain",
|
||||
"symbol.rivencraft.terrain.amplified.description": "Tall, sharply varied Overworld terrain.",
|
||||
"symbol.rivencraft.terrain.cave": "Cave Terrain",
|
||||
"symbol.rivencraft.terrain.cave.description": "An enclosed cave-shaped world with a sheltered arrival.",
|
||||
"symbol.rivencraft.terrain.island": "Island Terrain",
|
||||
"symbol.rivencraft.terrain.island.description": "Floating-island terrain above the void.",
|
||||
"symbol.rivencraft.terrain.flat": "Flat Terrain",
|
||||
"symbol.rivencraft.terrain.flat.description": "A level Overworld-style landscape.",
|
||||
"symbol.rivencraft.terrain.void": "Void Terrain",
|
||||
"symbol.rivencraft.terrain.void.description": "An empty world with a safe arrival platform.",
|
||||
"symbol.rivencraft.biome.native": "Native Biomes",
|
||||
"symbol.rivencraft.biome.native.description": "Use the terrain profile's native biome climate.",
|
||||
"symbol.rivencraft.lighting.normal": "Normal Lighting",
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"schema_version": 2,
|
||||
"name": "symbol.rivencraft.terrain.amplified",
|
||||
"description": "symbol.rivencraft.terrain.amplified.description",
|
||||
"category": "rivencraft:terrain",
|
||||
"semantics": { "role": "terrain", "value": "rivencraft:amplified" },
|
||||
"poem": "Terrain Form Tradition Spur"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"schema_version": 2,
|
||||
"name": "symbol.rivencraft.terrain.cave",
|
||||
"description": "symbol.rivencraft.terrain.cave.description",
|
||||
"category": "rivencraft:terrain",
|
||||
"semantics": { "role": "terrain", "value": "rivencraft:cave" },
|
||||
"poem": "Terrain Transform Void Flow"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"schema_version": 2,
|
||||
"name": "symbol.rivencraft.terrain.flat",
|
||||
"description": "symbol.rivencraft.terrain.flat.description",
|
||||
"category": "rivencraft:terrain",
|
||||
"semantics": { "role": "terrain", "value": "rivencraft:flat" },
|
||||
"poem": "Terrain Form Inhibit Motion"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"schema_version": 2,
|
||||
"name": "symbol.rivencraft.terrain.island",
|
||||
"description": "symbol.rivencraft.terrain.island.description",
|
||||
"category": "rivencraft:terrain",
|
||||
"semantics": { "role": "terrain", "value": "rivencraft:island" },
|
||||
"poem": "Terrain Transform Form Celestial"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"schema_version": 2,
|
||||
"name": "symbol.rivencraft.terrain.void",
|
||||
"description": "symbol.rivencraft.terrain.void.description",
|
||||
"category": "rivencraft:terrain",
|
||||
"semantics": { "role": "terrain", "value": "rivencraft:void" },
|
||||
"poem": "Terrain Form Infinite Void"
|
||||
}
|
||||
Reference in new issue
Block a user