From 0e602f0154056b4fb9c22e7e21cc15366acc518e Mon Sep 17 00:00:00 2001 From: Dustin Norberg Date: Mon, 21 Sep 2026 06:50:01 -0400 Subject: [PATCH] Add descriptive book age references --- docs/PORTING.md | 6 ++++- gradle.properties | 2 +- .../rivencraft/book/AgeReference.java | 27 +++++++++++++++++++ .../rivencraft/item/DescriptiveBookItem.java | 15 +++++++++-- .../registry/ModDataComponents.java | 7 +++++ .../assets/rivencraft/lang/en_us.json | 1 + 6 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 src/main/java/net/dnorberg/rivencraft/book/AgeReference.java diff --git a/docs/PORTING.md b/docs/PORTING.md index edd8705..2d581e0 100644 --- a/docs/PORTING.md +++ b/docs/PORTING.md @@ -44,7 +44,7 @@ the symbol grammar is introduced. - [x] Server-authoritative cross-dimension travel with world-border, collision, fluid, and floor checks. - [x] Nearby landing search and clear failure messages. -- [ ] Descriptive Book identity and Age reference components. +- [x] Descriptive Book identity and Age reference components. - [ ] Return-link creation as part of Descriptive Book travel. - [ ] Lectern/book-stand interaction. - [ ] Automated in-game tests for binding, persistence, and travel. @@ -53,6 +53,10 @@ The temporary shapeless Linking Book recipe makes the linking subsystem easy to test. It can move into the writing/bookbinder progression when that system is implemented. +Age references use UUIDs rather than dimension keys. Milestone 3 will own the +mapping from that stable identity to server data and a runtime dimension. This +keeps saved books valid if an Age is migrated or regenerated. + ## Version policy Versions follow `x.y.z`: diff --git a/gradle.properties b/gradle.properties index e9d8b44..5c64f25 100644 --- a/gradle.properties +++ b/gradle.properties @@ -32,7 +32,7 @@ mod_name=Rivencraft # 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 # The mod version. See https://semver.org/ -mod_version=0.1.1 +mod_version=0.1.2 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/net/dnorberg/rivencraft/book/AgeReference.java b/src/main/java/net/dnorberg/rivencraft/book/AgeReference.java new file mode 100644 index 0000000..e6a2c37 --- /dev/null +++ b/src/main/java/net/dnorberg/rivencraft/book/AgeReference.java @@ -0,0 +1,27 @@ +package net.dnorberg.rivencraft.book; + +import com.mojang.serialization.Codec; +import io.netty.buffer.ByteBuf; +import java.util.UUID; +import net.minecraft.core.UUIDUtil; +import net.minecraft.network.codec.StreamCodec; + +/** + * Stable identity for an Age, independent of its eventual dimension key. + * + *

Milestone 3 will resolve this identifier through server-owned Age data. + * Books only retain the identifier so dimension regeneration or migration does + * not invalidate every existing book.

+ */ +public record AgeReference(UUID id) { + public static final Codec CODEC = UUIDUtil.STRING_CODEC.xmap(AgeReference::new, AgeReference::id); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + UUIDUtil.STREAM_CODEC, + AgeReference::id, + AgeReference::new + ); + + public String shortId() { + return id.toString().substring(0, 8); + } +} diff --git a/src/main/java/net/dnorberg/rivencraft/item/DescriptiveBookItem.java b/src/main/java/net/dnorberg/rivencraft/item/DescriptiveBookItem.java index d0bf781..42f4469 100644 --- a/src/main/java/net/dnorberg/rivencraft/item/DescriptiveBookItem.java +++ b/src/main/java/net/dnorberg/rivencraft/item/DescriptiveBookItem.java @@ -1,8 +1,11 @@ package net.dnorberg.rivencraft.item; import java.util.List; +import net.dnorberg.rivencraft.book.AgeReference; +import net.dnorberg.rivencraft.registry.ModDataComponents; import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.TooltipFlag; @@ -19,6 +22,11 @@ public final class DescriptiveBookItem extends Item { super(properties); } + @Override + public boolean isFoil(ItemStack stack) { + return stack.has(ModDataComponents.AGE_REFERENCE.get()) || super.isFoil(stack); + } + @Override public void appendHoverText( ItemStack stack, @@ -26,7 +34,10 @@ public final class DescriptiveBookItem extends Item { List tooltipComponents, TooltipFlag tooltipFlag ) { - tooltipComponents.add(Component.translatable("item.rivencraft.descriptive_book.unwritten") - .withStyle(ChatFormatting.GRAY)); + AgeReference age = stack.get(ModDataComponents.AGE_REFERENCE.get()); + MutableComponent status = age == null + ? Component.translatable("item.rivencraft.descriptive_book.unwritten") + : Component.translatable("item.rivencraft.descriptive_book.age", age.shortId()); + tooltipComponents.add(status.withStyle(ChatFormatting.GRAY)); } } diff --git a/src/main/java/net/dnorberg/rivencraft/registry/ModDataComponents.java b/src/main/java/net/dnorberg/rivencraft/registry/ModDataComponents.java index 6f4efa5..0c2f3d0 100644 --- a/src/main/java/net/dnorberg/rivencraft/registry/ModDataComponents.java +++ b/src/main/java/net/dnorberg/rivencraft/registry/ModDataComponents.java @@ -2,6 +2,7 @@ package net.dnorberg.rivencraft.registry; import net.dnorberg.rivencraft.Rivencraft; import net.dnorberg.rivencraft.book.LinkTarget; +import net.dnorberg.rivencraft.book.AgeReference; import net.minecraft.core.component.DataComponentType; import net.minecraft.core.registries.Registries; import net.neoforged.bus.api.IEventBus; @@ -18,6 +19,12 @@ public final class ModDataComponents { .networkSynchronized(LinkTarget.STREAM_CODEC) .cacheEncoding() .build()); + public static final DeferredHolder, DataComponentType> AGE_REFERENCE = + DATA_COMPONENTS.register("age_reference", () -> DataComponentType.builder() + .persistent(AgeReference.CODEC) + .networkSynchronized(AgeReference.STREAM_CODEC) + .cacheEncoding() + .build()); private ModDataComponents() { } diff --git a/src/main/resources/assets/rivencraft/lang/en_us.json b/src/main/resources/assets/rivencraft/lang/en_us.json index 2dbe910..13b6484 100644 --- a/src/main/resources/assets/rivencraft/lang/en_us.json +++ b/src/main/resources/assets/rivencraft/lang/en_us.json @@ -1,6 +1,7 @@ { "item.rivencraft.descriptive_book": "Descriptive Book", "item.rivencraft.descriptive_book.unwritten": "Unwritten", + "item.rivencraft.descriptive_book.age": "Age %s", "item.rivencraft.linking_book": "Linking Book", "item.rivencraft.linking_book.unbound": "Unbound — sneak-use to bind", "item.rivencraft.linking_book.destination": "%s (%s, %s, %s)",