Public Access
This commit is contained in:
1 parent
27abebe3aa
commit
28c2ca0db3
15 files changed
+369
-62
No files matched your search
@@ -5,6 +5,9 @@ import java.util.List;
|
||||
import java.util.UUID;
|
||||
import net.dnorberg.rivencraft.book.AgeReference;
|
||||
import net.dnorberg.rivencraft.book.AgeRegistrySavedData;
|
||||
import net.dnorberg.rivencraft.book.AgeGenerationPlan;
|
||||
import net.dnorberg.rivencraft.book.AgeGenerationProfiles;
|
||||
import net.dnorberg.rivencraft.book.AgeRuntimeProfiles;
|
||||
import net.dnorberg.rivencraft.item.PageItem;
|
||||
import net.dnorberg.rivencraft.registry.ModBlockEntities;
|
||||
import net.dnorberg.rivencraft.registry.ModDataComponents;
|
||||
@@ -14,6 +17,7 @@ import net.dnorberg.rivencraft.writing.OrderedWriting;
|
||||
import net.dnorberg.rivencraft.writing.PageContent;
|
||||
import net.dnorberg.rivencraft.writing.WritingGrammar;
|
||||
import net.dnorberg.rivencraft.writing.WritingEntry;
|
||||
import net.dnorberg.rivencraft.writing.SymbolSemantics;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.core.NonNullList;
|
||||
@@ -109,11 +113,29 @@ public final class BookBinderBlockEntity extends BlockEntity implements Containe
|
||||
if (!page.isEmpty() && (!validPage(page) || !PageItem.isUsableWriting(page) || page.getCount() != 1
|
||||
|| PageItem.content(page).kind() == PageContent.Kind.BLANK)) return false;
|
||||
}
|
||||
return WritingGrammar.parse(currentWriting()).canCreate();
|
||||
OrderedWriting writing = currentWriting();
|
||||
if (!writingGrammar().canCreate()) return false;
|
||||
try {
|
||||
AgeGenerationPlan.compile(writing, 0L, AgeGenerationProfiles.STANDARD);
|
||||
return true;
|
||||
} catch (IllegalArgumentException exception) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public WritingGrammar.Result writingGrammar() {
|
||||
return WritingGrammar.parse(currentWriting());
|
||||
WritingGrammar.Result parsed = WritingGrammar.parse(currentWriting());
|
||||
if (!parsed.canCreate()) return parsed;
|
||||
var terrain = parsed.symbols().stream()
|
||||
.filter(symbol -> symbol.semantics().role() == SymbolSemantics.Role.TERRAIN)
|
||||
.findFirst();
|
||||
if (terrain.isEmpty()) return parsed;
|
||||
var profile = ((SymbolSemantics.ResourceValue) terrain.orElseThrow().semantics().value()).id();
|
||||
if (AgeRuntimeProfiles.profiles().containsKey(profile)) return parsed;
|
||||
List<WritingGrammar.Diagnostic> diagnostics = new ArrayList<>(parsed.diagnostics());
|
||||
diagnostics.add(new WritingGrammar.Diagnostic(WritingGrammar.Code.UNSUPPORTED_PROFILE,
|
||||
terrain.orElseThrow().index(), "Restore the Age profile " + profile + " or remove this page"));
|
||||
return new WritingGrammar.Result(parsed.symbols(), diagnostics);
|
||||
}
|
||||
|
||||
/** Map the parser's compact entry index back to the visible strip, including empty page positions. */
|
||||
@@ -163,7 +185,7 @@ public final class BookBinderBlockEntity extends BlockEntity implements Containe
|
||||
if (!canAssemble()) return false;
|
||||
if (pendingAgeId == null) pendingAgeId = UUID.randomUUID();
|
||||
if (level instanceof ServerLevel serverLevel)
|
||||
AgeRegistrySavedData.resolveOrCreate(serverLevel, new AgeReference(pendingAgeId, title));
|
||||
AgeRegistrySavedData.resolveOrCreate(serverLevel, new AgeReference(pendingAgeId, title), currentWriting());
|
||||
items.get(COVER).shrink(1);
|
||||
if (items.get(COVER).isEmpty()) items.set(COVER, ItemStack.EMPTY);
|
||||
items.get(LINK_PANEL).shrink(1);
|
||||
|
||||
@@ -16,12 +16,13 @@ public record AgeDefinition(
|
||||
long createdGameTime,
|
||||
ResourceLocation dimensionId,
|
||||
BlockPos spawn,
|
||||
AgeTraits traits
|
||||
AgeTraits traits,
|
||||
AgeGenerationPlan plan
|
||||
) {
|
||||
public static final ResourceLocation RANDOM_VANILLA_PROFILE = ResourceLocation.fromNamespaceAndPath(
|
||||
Rivencraft.MOD_ID, "random_vanilla"
|
||||
);
|
||||
public static final int CURRENT_GENERATION_VERSION = 13;
|
||||
public static final int CURRENT_GENERATION_VERSION = 14;
|
||||
|
||||
public AgeDefinition {
|
||||
Objects.requireNonNull(id, "id");
|
||||
@@ -29,6 +30,16 @@ public record AgeDefinition(
|
||||
Objects.requireNonNull(generationProfile, "generationProfile");
|
||||
Objects.requireNonNull(dimensionId, "dimensionId");
|
||||
Objects.requireNonNull(traits, "traits");
|
||||
if (plan != null && plan.ageSeed() != seed) {
|
||||
throw new IllegalArgumentException("Age plan seed does not match its Age definition");
|
||||
}
|
||||
}
|
||||
|
||||
public AgeDefinition(UUID id, String name, long seed, ResourceLocation generationProfile,
|
||||
int generationVersion, long createdGameTime, ResourceLocation dimensionId,
|
||||
BlockPos spawn, AgeTraits traits) {
|
||||
this(id, name, seed, generationProfile, generationVersion, createdGameTime,
|
||||
dimensionId, spawn, traits, null);
|
||||
}
|
||||
|
||||
public static ResourceLocation dimensionId(UUID ageId) {
|
||||
@@ -59,6 +70,6 @@ public record AgeDefinition(
|
||||
|
||||
public AgeDefinition withSpawn(BlockPos spawn) {
|
||||
return new AgeDefinition(id, name, seed, generationProfile, generationVersion,
|
||||
createdGameTime, dimensionId, spawn.immutable(), traits);
|
||||
createdGameTime, dimensionId, spawn.immutable(), traits, plan);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import java.util.Comparator;
|
||||
import java.util.Collections;
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.dnorberg.rivencraft.compat.ImmersivePortalsChunkLoaders;
|
||||
import net.dnorberg.rivencraft.writing.OrderedWriting;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.GlobalPos;
|
||||
import net.minecraft.core.Holder;
|
||||
@@ -73,7 +74,12 @@ public final class AgeDimensionService {
|
||||
}
|
||||
|
||||
public static Optional<LinkTarget> resolve(ServerPlayer player, AgeReference reference) {
|
||||
AgeDefinition definition = AgeRegistrySavedData.resolveOrCreate(player.serverLevel(), reference);
|
||||
return resolve(player, reference, OrderedWriting.EMPTY);
|
||||
}
|
||||
|
||||
public static Optional<LinkTarget> resolve(ServerPlayer player, AgeReference reference,
|
||||
OrderedWriting writing) {
|
||||
AgeDefinition definition = AgeRegistrySavedData.resolveOrCreate(player.serverLevel(), reference, writing);
|
||||
ServerLevel level = ensureLoaded(player.getServer(), definition);
|
||||
if (level == null) {
|
||||
return Optional.empty();
|
||||
@@ -261,7 +267,8 @@ public final class AgeDimensionService {
|
||||
}
|
||||
|
||||
private static ChunkGenerator createGenerator(MinecraftServer server, AgeDefinition age) {
|
||||
AgeRuntimeProfile profile = AgeRuntimeProfiles.resolve(age.generationProfile());
|
||||
AgeRuntimeProfile profile = age.plan() == null ? AgeRuntimeProfiles.resolve(age.generationProfile())
|
||||
: age.plan().runtimeProfile();
|
||||
if (profile.terrain() == AgeRuntimeProfile.Terrain.FLAT) {
|
||||
return new FlatLevelSource(FlatLevelGeneratorSettings.getDefault(
|
||||
server.registryAccess().lookupOrThrow(Registries.BIOME),
|
||||
@@ -385,7 +392,8 @@ public final class AgeDimensionService {
|
||||
}
|
||||
|
||||
private static DimensionType createDimensionType(MinecraftServer server, AgeDefinition age) {
|
||||
AgeRuntimeProfile profile = AgeRuntimeProfiles.resolve(age.generationProfile());
|
||||
AgeRuntimeProfile profile = age.plan() == null ? AgeRuntimeProfiles.resolve(age.generationProfile())
|
||||
: age.plan().runtimeProfile();
|
||||
DimensionType base = server.registryAccess().lookupOrThrow(Registries.DIMENSION_TYPE)
|
||||
.getOrThrow(ResourceKey.create(Registries.DIMENSION_TYPE, profile.dimensionType())).value();
|
||||
AgeTraits.Lighting lighting = age.traits().lighting();
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
package net.dnorberg.rivencraft.book;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import net.dnorberg.rivencraft.writing.OrderedWriting;
|
||||
import net.dnorberg.rivencraft.writing.SymbolReference;
|
||||
import net.dnorberg.rivencraft.writing.SymbolSemantics;
|
||||
import net.dnorberg.rivencraft.writing.WritingEntry;
|
||||
import net.dnorberg.rivencraft.writing.WritingGrammar;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
/** A frozen interpretation of authored pages. Later slices consume its resolved branches. */
|
||||
public record AgeGenerationPlan(int version, long ageSeed, OrderedWriting writing, List<ResolvedSymbol> symbols,
|
||||
AgeRuntimeProfile runtimeProfile) {
|
||||
public static final int CURRENT_VERSION = 1;
|
||||
|
||||
public AgeGenerationPlan {
|
||||
if (version != CURRENT_VERSION) throw new IllegalArgumentException("Unsupported Age plan version " + version);
|
||||
Objects.requireNonNull(writing, "writing");
|
||||
symbols = List.copyOf(symbols);
|
||||
Objects.requireNonNull(runtimeProfile, "runtimeProfile");
|
||||
if (runtimeProfile.schemaVersion() != AgeRuntimeProfile.CURRENT_SCHEMA_VERSION) {
|
||||
throw new IllegalArgumentException("Unsupported runtime profile snapshot version");
|
||||
}
|
||||
for (ResolvedSymbol symbol : symbols) {
|
||||
if (symbol.index() < 0 || symbol.index() >= writing.entries().size()
|
||||
|| !symbol.reference().equals(writing.entries().get(symbol.index()).symbol().orElse(null))) {
|
||||
throw new IllegalArgumentException("Resolved symbol does not match original page order");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** No plan is made for old page-free books or Link-Panel-only random Ages. */
|
||||
public static AgeGenerationPlan compile(OrderedWriting writing, long ageSeed, ResourceLocation seedProfile) {
|
||||
WritingGrammar.Result parsed = WritingGrammar.parse(writing);
|
||||
if (!parsed.canCreate()) throw new IllegalArgumentException(parsed.firstError().orElseThrow().message());
|
||||
if (parsed.randomFallback()) return null;
|
||||
ResourceLocation terrain = parsed.symbols().stream()
|
||||
.filter(symbol -> symbol.semantics().role() == SymbolSemantics.Role.TERRAIN)
|
||||
.map(symbol -> ((SymbolSemantics.ResourceValue) symbol.semantics().value()).id())
|
||||
.findFirst().orElse(null);
|
||||
// The seed-selected terrain remains the deterministic completion for a missing branch.
|
||||
// An explicit terrain must resolve now, before Binder inputs can be consumed.
|
||||
if (terrain != null && !AgeRuntimeProfiles.profiles().containsKey(terrain)) {
|
||||
throw new IllegalArgumentException("Unsupported Age terrain profile " + terrain);
|
||||
}
|
||||
ResourceLocation selected = terrain == null ? seedProfile : terrain;
|
||||
AgeRuntimeProfile profile = AgeRuntimeProfiles.profiles().get(selected);
|
||||
if (profile == null) throw new IllegalArgumentException("Unsupported Age terrain profile " + selected);
|
||||
return new AgeGenerationPlan(CURRENT_VERSION, ageSeed, writing,
|
||||
parsed.symbols().stream().map(symbol -> new ResolvedSymbol(
|
||||
symbol.index(), symbol.reference(), symbol.semantics())).toList(),
|
||||
profile);
|
||||
}
|
||||
|
||||
public ResourceLocation authoredTerrain() {
|
||||
return symbols.stream().filter(symbol -> symbol.semantics().role() == SymbolSemantics.Role.TERRAIN)
|
||||
.map(symbol -> ((SymbolSemantics.ResourceValue) symbol.semantics().value()).id())
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
|
||||
public CompoundTag save() {
|
||||
CompoundTag tag = new CompoundTag();
|
||||
tag.putInt("Version", version);
|
||||
tag.putLong("AgeSeed", ageSeed);
|
||||
ListTag pages = new ListTag();
|
||||
for (WritingEntry entry : writing.entries()) {
|
||||
CompoundTag page = new CompoundTag();
|
||||
page.putString("Kind", entry.kind().name());
|
||||
entry.symbol().ifPresent(symbol -> page.putString("Symbol", symbol.id().toString()));
|
||||
pages.add(page);
|
||||
}
|
||||
tag.put("Writing", pages);
|
||||
ListTag resolved = new ListTag();
|
||||
for (ResolvedSymbol symbol : symbols) {
|
||||
CompoundTag item = new CompoundTag();
|
||||
item.putInt("Index", symbol.index());
|
||||
item.putString("Symbol", symbol.reference().id().toString());
|
||||
item.putString("Role", symbol.semantics().role().name());
|
||||
SymbolSemantics.Value value = symbol.semantics().value();
|
||||
item.putString("Value", value instanceof SymbolSemantics.ResourceValue resource
|
||||
? resource.id().toString() : value instanceof SymbolSemantics.ChoiceValue choice
|
||||
? choice.id() : Double.toString(((SymbolSemantics.NumberValue) value).amount()));
|
||||
resolved.add(item);
|
||||
}
|
||||
tag.put("Resolved", resolved);
|
||||
CompoundTag profile = new CompoundTag();
|
||||
profile.putInt("Version", runtimeProfile.schemaVersion());
|
||||
profile.putString("Terrain", runtimeProfile.terrain().name());
|
||||
profile.putString("Noise", runtimeProfile.noiseSettings().toString());
|
||||
profile.putString("Biomes", runtimeProfile.biomePreset().toString());
|
||||
profile.putString("Dimension", runtimeProfile.dimensionType().toString());
|
||||
tag.put("RuntimeProfile", profile);
|
||||
return tag;
|
||||
}
|
||||
|
||||
public static AgeGenerationPlan load(CompoundTag tag) {
|
||||
int version = tag.getInt("Version");
|
||||
List<WritingEntry> pages = new ArrayList<>();
|
||||
ListTag writingTags = tag.getList("Writing", CompoundTag.TAG_COMPOUND);
|
||||
for (int index = 0; index < writingTags.size(); index++) {
|
||||
CompoundTag page = writingTags.getCompound(index);
|
||||
pages.add(WritingEntry.Kind.valueOf(page.getString("Kind")) == WritingEntry.Kind.LINK_PANEL
|
||||
? WritingEntry.linkPanel() : WritingEntry.symbol(new SymbolReference(
|
||||
ResourceLocation.parse(page.getString("Symbol")))));
|
||||
}
|
||||
List<ResolvedSymbol> resolved = new ArrayList<>();
|
||||
ListTag resolvedTags = tag.getList("Resolved", CompoundTag.TAG_COMPOUND);
|
||||
for (int index = 0; index < resolvedTags.size(); index++) {
|
||||
CompoundTag item = resolvedTags.getCompound(index);
|
||||
SymbolSemantics.Role role = SymbolSemantics.Role.valueOf(item.getString("Role"));
|
||||
String raw = item.getString("Value");
|
||||
SymbolSemantics.Value value = switch (role) {
|
||||
case TERRAIN, BIOME, FEATURE -> new SymbolSemantics.ResourceValue(ResourceLocation.parse(raw));
|
||||
case PERIOD_MODIFIER, ANGLE_MODIFIER, PHASE_MODIFIER ->
|
||||
new SymbolSemantics.NumberValue(Double.parseDouble(raw));
|
||||
default -> new SymbolSemantics.ChoiceValue(raw);
|
||||
};
|
||||
resolved.add(new ResolvedSymbol(item.getInt("Index"),
|
||||
new SymbolReference(ResourceLocation.parse(item.getString("Symbol"))),
|
||||
new SymbolSemantics(role, value)));
|
||||
}
|
||||
CompoundTag profile = tag.getCompound("RuntimeProfile");
|
||||
return new AgeGenerationPlan(version, tag.getLong("AgeSeed"), new OrderedWriting(pages), resolved,
|
||||
new AgeRuntimeProfile(profile.getInt("Version"),
|
||||
AgeRuntimeProfile.Terrain.valueOf(profile.getString("Terrain")),
|
||||
ResourceLocation.parse(profile.getString("Noise")),
|
||||
ResourceLocation.parse(profile.getString("Biomes")),
|
||||
ResourceLocation.parse(profile.getString("Dimension"))));
|
||||
}
|
||||
|
||||
public record ResolvedSymbol(int index, SymbolReference reference, SymbolSemantics semantics) {
|
||||
public ResolvedSymbol {
|
||||
Objects.requireNonNull(reference, "reference");
|
||||
Objects.requireNonNull(semantics, "semantics");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
package net.dnorberg.rivencraft.book;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.List;
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
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;
|
||||
@@ -18,6 +22,54 @@ public final class AgePersistenceGameTests {
|
||||
private AgePersistenceGameTests() {
|
||||
}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void authoredPlanRetainsResolvedMeaningAndAgeIdentity(GameTestHelper helper) {
|
||||
OrderedWriting writing = new OrderedWriting(List.of(
|
||||
WritingEntry.linkPanel(),
|
||||
WritingEntry.symbol(new SymbolReference(ResourceLocation.parse("rivencraft:terrain/standard"))),
|
||||
WritingEntry.symbol(new SymbolReference(ResourceLocation.parse("rivencraft:weather/normal"))),
|
||||
WritingEntry.symbol(new SymbolReference(ResourceLocation.parse("rivencraft:lighting/normal"))),
|
||||
WritingEntry.symbol(new SymbolReference(ResourceLocation.parse("rivencraft:celestial/normal_time"))),
|
||||
WritingEntry.symbol(new SymbolReference(ResourceLocation.parse("rivencraft:feature/caves")))
|
||||
));
|
||||
UUID id = UUID.randomUUID();
|
||||
AgeReference reference = new AgeReference(id, "Authored Plan " + id);
|
||||
AgeDefinition created = AgeRegistrySavedData.resolveOrCreate(helper.getLevel(), reference, writing);
|
||||
helper.assertTrue(created.plan() != null, "Authored writing must create a frozen plan");
|
||||
helper.assertValueEqual(created.plan().version(), AgeGenerationPlan.CURRENT_VERSION,
|
||||
"The plan format must be explicitly versioned");
|
||||
helper.assertValueEqual(created.plan().ageSeed(), created.seed(),
|
||||
"The Age seed must be captured in the immutable plan");
|
||||
helper.assertValueEqual(created.plan().writing(), writing, "The original page order must be retained");
|
||||
helper.assertValueEqual(created.plan().symbols().size(), 5, "Every symbol must be resolved in order");
|
||||
helper.assertValueEqual(created.generationProfile(), AgeGenerationProfiles.STANDARD,
|
||||
"Authored terrain must replace the seed-selected random terrain");
|
||||
helper.assertValueEqual(created.plan().runtimeProfile(), AgeRuntimeProfiles.resolve(AgeGenerationProfiles.STANDARD),
|
||||
"The plan must snapshot the runtime profile instead of looking it up after reload");
|
||||
|
||||
AgeDefinition sameId = AgeRegistrySavedData.resolveOrCreate(helper.getLevel(),
|
||||
new AgeReference(id, "Renamed"), OrderedWriting.EMPTY);
|
||||
helper.assertValueEqual(sameId, created, "The same Age UUID must never reroll from a later book");
|
||||
AgeRegistrySavedData registry = AgeRegistrySavedData.get(helper.getLevel());
|
||||
AgeDefinition restored = AgeRegistrySavedData.load(
|
||||
registry.save(new CompoundTag(), helper.getLevel().registryAccess()),
|
||||
helper.getLevel().registryAccess()).find(id).orElseThrow();
|
||||
helper.assertValueEqual(restored, created, "The complete plan must survive a saved-data round trip");
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void randomFallbackDoesNotCreateAuthoredPlan(GameTestHelper helper) {
|
||||
AgeDefinition pageFree = AgeRegistrySavedData.resolveOrCreate(helper.getLevel(),
|
||||
new AgeReference(UUID.randomUUID(), "Page Free"), OrderedWriting.EMPTY);
|
||||
AgeDefinition panelOnly = AgeRegistrySavedData.resolveOrCreate(helper.getLevel(),
|
||||
new AgeReference(UUID.randomUUID(), "Panel Only"),
|
||||
new OrderedWriting(List.of(WritingEntry.linkPanel())));
|
||||
helper.assertTrue(pageFree.plan() == null && panelOnly.plan() == null,
|
||||
"Old and Link-Panel-only books must retain the random Age path");
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void everyTraitCombinationRoundTripsWithoutRerolling(GameTestHelper helper) {
|
||||
ListTag entries = new ListTag();
|
||||
@@ -89,6 +141,8 @@ public final class AgePersistenceGameTests {
|
||||
"Definitions predating named dimensions must retain the UUID-derived dimension");
|
||||
helper.assertValueEqual(definition.traits(), AgeTraits.LEGACY_DEFAULTS,
|
||||
"Definitions predating traits must receive stable legacy defaults");
|
||||
helper.assertTrue(definition.plan() == null,
|
||||
"Existing Ages must not be reinterpreted as authored plans during migration");
|
||||
|
||||
CompoundTag serialized = migrated.save(new CompoundTag(), helper.getLevel().registryAccess());
|
||||
AgeDefinition reloaded = AgeRegistrySavedData.load(serialized, helper.getLevel().registryAccess())
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.List;
|
||||
import net.dnorberg.rivencraft.writing.OrderedWriting;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
@@ -29,6 +30,7 @@ public final class AgeRegistrySavedData extends SavedData {
|
||||
private static final String SPAWN_Y_TAG = "SpawnY";
|
||||
private static final String SPAWN_Z_TAG = "SpawnZ";
|
||||
private static final String TRAITS_TAG = "Traits";
|
||||
private static final String PLAN_TAG = "GenerationPlan";
|
||||
private static final String BIOME_DISTRIBUTION_TAG = "BiomeDistribution";
|
||||
private static final String LIGHTING_TAG = "Lighting";
|
||||
private static final String WEATHER_TAG = "Weather";
|
||||
@@ -49,8 +51,12 @@ public final class AgeRegistrySavedData extends SavedData {
|
||||
|
||||
/** Resolves a book identity, creating its deterministic random definition exactly once. */
|
||||
public static AgeDefinition resolveOrCreate(ServerLevel level, AgeReference reference) {
|
||||
return resolveOrCreate(level, reference, OrderedWriting.EMPTY);
|
||||
}
|
||||
|
||||
public static AgeDefinition resolveOrCreate(ServerLevel level, AgeReference reference, OrderedWriting writing) {
|
||||
ServerLevel overworld = level.getServer().overworld();
|
||||
return get(overworld).getOrCreate(reference, overworld.getSeed(), overworld.getGameTime());
|
||||
return get(overworld).getOrCreate(reference, writing, overworld.getSeed(), overworld.getGameTime());
|
||||
}
|
||||
|
||||
public Optional<AgeDefinition> find(UUID ageId) {
|
||||
@@ -78,7 +84,8 @@ public final class AgeRegistrySavedData extends SavedData {
|
||||
return updated;
|
||||
}
|
||||
|
||||
private AgeDefinition getOrCreate(AgeReference reference, long worldSeed, long createdGameTime) {
|
||||
private AgeDefinition getOrCreate(AgeReference reference, OrderedWriting writing,
|
||||
long worldSeed, long createdGameTime) {
|
||||
AgeDefinition existing = ages.get(reference.id());
|
||||
if (existing != null) {
|
||||
return existing;
|
||||
@@ -87,16 +94,19 @@ public final class AgeRegistrySavedData extends SavedData {
|
||||
String name = normalizedName(reference.name());
|
||||
ResourceLocation dimensionId = availableDimensionId(name);
|
||||
long seed = ageSeed(worldSeed, dimensionId);
|
||||
ResourceLocation seedProfile = AgeRuntimeProfiles.select(seed);
|
||||
AgeGenerationPlan plan = AgeGenerationPlan.compile(writing, seed, seedProfile);
|
||||
AgeDefinition created = new AgeDefinition(
|
||||
reference.id(),
|
||||
name,
|
||||
seed,
|
||||
AgeRuntimeProfiles.select(seed),
|
||||
plan == null || plan.authoredTerrain() == null ? seedProfile : plan.authoredTerrain(),
|
||||
AgeDefinition.CURRENT_GENERATION_VERSION,
|
||||
createdGameTime,
|
||||
dimensionId,
|
||||
null,
|
||||
AgeTraits.select(seed)
|
||||
AgeTraits.select(seed),
|
||||
plan
|
||||
);
|
||||
ages.put(created.id(), created);
|
||||
setDirty();
|
||||
@@ -138,7 +148,9 @@ public final class AgeRegistrySavedData extends SavedData {
|
||||
entry.getLong(CREATED_TIME_TAG),
|
||||
dimensionId,
|
||||
spawn,
|
||||
readTraits(entry)
|
||||
readTraits(entry),
|
||||
entry.contains(PLAN_TAG, CompoundTag.TAG_COMPOUND)
|
||||
? AgeGenerationPlan.load(entry.getCompound(PLAN_TAG)) : null
|
||||
));
|
||||
}
|
||||
return data;
|
||||
@@ -171,6 +183,7 @@ public final class AgeRegistrySavedData extends SavedData {
|
||||
traits.putFloat(CELESTIAL_ANGLE_TAG, age.traits().celestialAngle());
|
||||
traits.putFloat(CELESTIAL_PHASE_TAG, age.traits().celestialPhase());
|
||||
entry.put(TRAITS_TAG, traits);
|
||||
if (age.plan() != null) entry.put(PLAN_TAG, age.plan().save());
|
||||
serializedAges.add(entry);
|
||||
});
|
||||
tag.put(AGES_TAG, serializedAges);
|
||||
|
||||
@@ -5,6 +5,8 @@ import net.dnorberg.rivencraft.book.LinkingService;
|
||||
import net.dnorberg.rivencraft.book.AgeReference;
|
||||
import net.dnorberg.rivencraft.book.AgeRegistrySavedData;
|
||||
import net.dnorberg.rivencraft.book.AgeDimensionService;
|
||||
import net.dnorberg.rivencraft.book.AgeGenerationPlan;
|
||||
import net.dnorberg.rivencraft.book.AgeGenerationProfiles;
|
||||
import net.dnorberg.rivencraft.block.entity.BookStandBlockEntity;
|
||||
import net.dnorberg.rivencraft.client.ClientLinkPreviewHandler;
|
||||
import net.dnorberg.rivencraft.client.ClientBookInteractionHandler;
|
||||
@@ -141,11 +143,21 @@ public final class ModNetworking {
|
||||
grammar.firstError().orElseThrow().message()), true);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
AgeGenerationPlan.compile(access.stack()
|
||||
.getOrDefault(ModDataComponents.BOOK_WRITING.get(), OrderedWriting.EMPTY),
|
||||
0L, AgeGenerationProfiles.STANDARD);
|
||||
} catch (IllegalArgumentException exception) {
|
||||
player.displayClientMessage(Component.translatable("message.rivencraft.descriptive_book.invalid_writing",
|
||||
exception.getMessage()), true);
|
||||
return;
|
||||
}
|
||||
|
||||
WRITING_BOOKS.add(access.stack());
|
||||
try {
|
||||
AgeReference reference = new AgeReference(UUID.randomUUID(), title);
|
||||
if (AgeDimensionService.resolve(player, reference).isEmpty()) {
|
||||
if (AgeDimensionService.resolve(player, reference, access.stack()
|
||||
.getOrDefault(ModDataComponents.BOOK_WRITING.get(), OrderedWriting.EMPTY)).isEmpty()) {
|
||||
player.displayClientMessage(Component.translatable("message.rivencraft.descriptive_book.unavailable"), true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ public final class WritingGrammar {
|
||||
|
||||
public enum Code {
|
||||
TOO_MANY, MISSING_PANEL, PANEL_ORDER, DUPLICATE_PANEL, UNRESOLVED, UNSUPPORTED,
|
||||
DUPLICATE, CONFLICT, OUT_OF_ORDER, MISPLACED_MODIFIER, INCOMPLETE
|
||||
DUPLICATE, CONFLICT, OUT_OF_ORDER, MISPLACED_MODIFIER, INCOMPLETE, UNSUPPORTED_PROFILE
|
||||
}
|
||||
|
||||
public record Diagnostic(Code code, int index, String message) {
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
"screen.rivencraft.book_binder.grammar.duplicate_panel": "Page %s: remove the extra Link Panel",
|
||||
"screen.rivencraft.book_binder.grammar.unresolved": "Page %s: missing symbol definition; restore its data pack or remove it",
|
||||
"screen.rivencraft.book_binder.grammar.unsupported": "Page %s: this symbol needs semantic schema v2; update its data pack or remove it",
|
||||
"screen.rivencraft.book_binder.grammar.unsupported_profile": "Page %s: its Age profile is unavailable; restore the data pack or remove this page",
|
||||
"screen.rivencraft.book_binder.grammar.duplicate": "Page %s: remove the duplicate symbol",
|
||||
"screen.rivencraft.book_binder.grammar.conflict": "Page %s: replace the conflicting symbol",
|
||||
"screen.rivencraft.book_binder.grammar.out_of_order": "Page %s: move this symbol before later categories",
|
||||
|
||||
Reference in new issue
Block a user