Show Age traits in descriptive books
Build / build (push) Canceled after 0s

This commit is contained in:
dnorberg committed 2026-09-22 17:23:01 -04:00
1 parent 3ad518232a
commit 73051c5ba6
7 files changed
+133 -22

No files matched your search

+6
View File
@@ -329,6 +329,12 @@ Age-seeded biomes in the legacy diagonal-checkerboard layout. Each square is
uses Minecraft's serializable checkerboard biome source so the layout remains uses Minecraft's serializable checkerboard biome source so the layout remains
stable across server reloads. stable across server reloads.
Version 0.2.22.1 includes the server-owned Age traits in the validated
Descriptive Book screen response and displays the terrain, biome distribution,
lighting, weather, day length, celestial angle, and celestial phase on the
book's right-hand description page. This works for held books and Book Stands
without copying mutable Age-generation data onto the item.
Version 0.2.14.0 restores the legacy Linking and Descriptive Book artwork for Version 0.2.14.0 restores the legacy Linking and Descriptive Book artwork for
inventory items and gives each book its matching open-book texture when placed inventory items and gives each book its matching open-book texture when placed
on a Book Stand. Return Linking Books created inside another Age also retain and on a Book Stand. Return Linking Books created inside another Age also retain and
+1 -1
View File
@@ -35,7 +35,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.2.22.0 mod_version=0.2.22.1
immersive_portals_version=6.0.7 immersive_portals_version=6.0.7
# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # 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. # This should match the base package used for the mod sources.
@@ -92,8 +92,8 @@ public final class ClientBookInteractionHandler {
return; return;
} }
minecraft.setScreen(payload.fromStand() minecraft.setScreen(payload.fromStand()
? new DescriptiveBookScreen(payload.source().pos(), payload.book()) ? new DescriptiveBookScreen(payload.source().pos(), payload.book(), payload.traits())
: new DescriptiveBookScreen(hand, payload.book())); : new DescriptiveBookScreen(hand, payload.book(), payload.traits()));
}); });
} }
} }
@@ -1,6 +1,7 @@
package net.dnorberg.rivencraft.client.screen; package net.dnorberg.rivencraft.client.screen;
import net.dnorberg.rivencraft.book.AgeReference; import net.dnorberg.rivencraft.book.AgeReference;
import net.dnorberg.rivencraft.book.AgeTraits;
import net.dnorberg.rivencraft.client.ClientBookExploreOverlay; import net.dnorberg.rivencraft.client.ClientBookExploreOverlay;
import net.dnorberg.rivencraft.network.UseDescriptiveBookPayload; import net.dnorberg.rivencraft.network.UseDescriptiveBookPayload;
import net.dnorberg.rivencraft.network.BindDescriptiveBookPayload; import net.dnorberg.rivencraft.network.BindDescriptiveBookPayload;
@@ -22,6 +23,9 @@ import net.minecraft.util.FormattedCharSequence;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionHand;
import net.neoforged.neoforge.network.PacketDistributor; import net.neoforged.neoforge.network.PacketDistributor;
import org.jetbrains.annotations.Nullable;
import java.util.Locale;
public final class DescriptiveBookScreen extends Screen implements ExplorableBookScreen { public final class DescriptiveBookScreen extends Screen implements ExplorableBookScreen {
private static final int BOOK_TEXT_COLOR = 0xFF3B281B; private static final int BOOK_TEXT_COLOR = 0xFF3B281B;
@@ -40,6 +44,7 @@ public final class DescriptiveBookScreen extends Screen implements ExplorableBoo
private final AgeReference age; private final AgeReference age;
private final ResourceLocation generationProfile; private final ResourceLocation generationProfile;
private final AgeTraits traits;
private final InteractionHand hand; private final InteractionHand hand;
private final BlockPos bookStandPos; private final BlockPos bookStandPos;
private DestinationPreviewRenderer preview; private DestinationPreviewRenderer preview;
@@ -47,18 +52,20 @@ public final class DescriptiveBookScreen extends Screen implements ExplorableBoo
private Button exploreButton; private Button exploreButton;
private boolean movingToExploreOverlay; private boolean movingToExploreOverlay;
public DescriptiveBookScreen(InteractionHand hand, ItemStack book) { public DescriptiveBookScreen(InteractionHand hand, ItemStack book, @Nullable AgeTraits traits) {
super(book.getHoverName()); super(book.getHoverName());
age = book.get(ModDataComponents.AGE_REFERENCE.get()); age = book.get(ModDataComponents.AGE_REFERENCE.get());
generationProfile = book.get(ModDataComponents.AGE_GENERATION_PROFILE.get()); generationProfile = book.get(ModDataComponents.AGE_GENERATION_PROFILE.get());
this.traits = traits;
this.hand = hand; this.hand = hand;
bookStandPos = null; bookStandPos = null;
} }
public DescriptiveBookScreen(BlockPos bookStandPos, ItemStack book) { public DescriptiveBookScreen(BlockPos bookStandPos, ItemStack book, @Nullable AgeTraits traits) {
super(book.getHoverName()); super(book.getHoverName());
age = book.get(ModDataComponents.AGE_REFERENCE.get()); age = book.get(ModDataComponents.AGE_REFERENCE.get());
generationProfile = book.get(ModDataComponents.AGE_GENERATION_PROFILE.get()); generationProfile = book.get(ModDataComponents.AGE_GENERATION_PROFILE.get());
this.traits = traits;
hand = InteractionHand.MAIN_HAND; hand = InteractionHand.MAIN_HAND;
this.bookStandPos = bookStandPos.immutable(); this.bookStandPos = bookStandPos.immutable();
} }
@@ -138,14 +145,34 @@ public final class DescriptiveBookScreen extends Screen implements ExplorableBoo
left + 177, top + 45, 124); left + 177, top + 45, 124);
} else { } else {
preview.render(graphics, left + LINK_PANEL_X, top + LINK_PANEL_Y, LINK_PANEL_WIDTH, LINK_PANEL_HEIGHT, partialTick); preview.render(graphics, left + LINK_PANEL_X, top + LINK_PANEL_Y, LINK_PANEL_WIDTH, LINK_PANEL_HEIGHT, partialTick);
drawWrappedWithoutShadow(graphics, Component.literal(age.name()), left + 177, top + 43, 124); int detailsY = drawWrappedWithoutShadow(
graphics, Component.literal(age.name()), left + 177, top + 43, 124) + 3;
detailsY = Math.max(detailsY, top + 72);
if (generationProfile != null) { if (generationProfile != null) {
Component profileName = generationProfile.getNamespace().equals("rivencraft") Component profileName = generationProfile.getNamespace().equals("rivencraft")
? Component.translatable("generator.rivencraft." + generationProfile.getPath()) ? Component.translatable("generator.rivencraft." + generationProfile.getPath())
: Component.literal(generationProfile.toString()); : Component.literal(generationProfile.toString());
drawWrappedWithoutShadow(graphics, detailsY = drawWrappedWithoutShadow(graphics,
Component.translatable("screen.rivencraft.age.generator", profileName), Component.translatable("screen.rivencraft.age.generator", profileName),
left + 177, top + 72, 124); left + 177, detailsY, 124) + 2;
}
if (traits != null) {
detailsY = drawTrait(graphics, "biomes", traitName("biome_distribution", traits.biomeDistribution()),
left + 177, detailsY);
detailsY = drawTrait(graphics, "lighting", traitName("lighting", traits.lighting()),
left + 177, detailsY);
detailsY = drawTrait(graphics, "weather", traitName("weather", traits.weather()),
left + 177, detailsY);
Component dayLength = traits.hasStoppedCelestialCycle()
? Component.translatable("trait.rivencraft.day_length.stopped")
: Component.literal(String.format(Locale.ROOT, "%.2fx", traits.celestialPeriod()));
detailsY = drawTrait(graphics, "day_length", dayLength, left + 177, detailsY);
detailsY = drawTrait(graphics, "celestial_angle",
Component.literal(String.format(Locale.ROOT, "%.0f°", traits.celestialAngle())),
left + 177, detailsY);
drawTrait(graphics, "celestial_phase",
Component.literal(String.format(Locale.ROOT, "%.0f%%", traits.celestialPhase() * 100.0F)),
left + 177, detailsY);
} }
} }
@@ -288,11 +315,22 @@ public final class DescriptiveBookScreen extends Screen implements ExplorableBoo
onClose(); onClose();
} }
private void drawWrappedWithoutShadow(GuiGraphics graphics, Component text, int x, int y, int maxWidth) { private int drawTrait(GuiGraphics graphics, String key, Component value, int x, int y) {
return drawWrappedWithoutShadow(graphics,
Component.translatable("screen.rivencraft.age.trait." + key, value), x, y, 124) + 2;
}
private static Component traitName(String group, Enum<?> value) {
return Component.translatable("trait.rivencraft." + group + "."
+ value.name().toLowerCase(Locale.ROOT));
}
private int drawWrappedWithoutShadow(GuiGraphics graphics, Component text, int x, int y, int maxWidth) {
for (FormattedCharSequence line : font.split(text, maxWidth)) { for (FormattedCharSequence line : font.split(text, maxWidth)) {
graphics.drawString(font, line, x, y, BOOK_TEXT_COLOR, false); graphics.drawString(font, line, x, y, BOOK_TEXT_COLOR, false);
y += font.lineHeight; y += font.lineHeight;
} }
return y;
} }
} }
@@ -75,18 +75,20 @@ public final class ModNetworking {
return; return;
} }
AgeReference reference = access.stack().get(ModDataComponents.AGE_REFERENCE.get()); AgeReference reference = access.stack().get(ModDataComponents.AGE_REFERENCE.get());
if (reference != null && !access.stack().has(ModDataComponents.AGE_GENERATION_PROFILE.get())) { var definition = reference == null
AgeRegistrySavedData.get(player.serverLevel()).find(reference.id()).ifPresent(definition -> { ? null
: AgeRegistrySavedData.get(player.serverLevel()).find(reference.id()).orElse(null);
if (definition != null && !access.stack().has(ModDataComponents.AGE_GENERATION_PROFILE.get())) {
access.stack().set(ModDataComponents.AGE_GENERATION_PROFILE.get(), definition.generationProfile()); access.stack().set(ModDataComponents.AGE_GENERATION_PROFILE.get(), definition.generationProfile());
access.sync().run(); access.sync().run();
});
} }
// Requests run on the same server thread as Age creation. A request queued // Requests run on the same server thread as Age creation. A request queued
// during generation can only open the finished book, never a stale client copy. // during generation can only open the finished book, never a stale client copy.
player.inventoryMenu.broadcastChanges(); player.inventoryMenu.broadcastChanges();
PacketDistributor.sendToPlayer(player, new OpenDescriptiveBookScreenPayload( PacketDistributor.sendToPlayer(player, new OpenDescriptiveBookScreenPayload(
payload.fromStand(), payload.offhand(), payload.fromStand(), payload.offhand(),
GlobalPos.of(player.level().dimension(), payload.standPos()), access.stack().copy())); GlobalPos.of(player.level().dimension(), payload.standPos()), access.stack().copy(),
definition == null ? null : definition.traits()));
} }
private static void bindDescriptiveBook(BindDescriptiveBookPayload payload, IPayloadContext context) { private static void bindDescriptiveBook(BindDescriptiveBookPayload payload, IPayloadContext context) {
@@ -1,27 +1,64 @@
package net.dnorberg.rivencraft.network; package net.dnorberg.rivencraft.network;
import net.dnorberg.rivencraft.Rivencraft; import net.dnorberg.rivencraft.Rivencraft;
import net.dnorberg.rivencraft.book.AgeTraits;
import net.minecraft.core.GlobalPos; import net.minecraft.core.GlobalPos;
import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload; import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.Nullable;
/** Server-approved book contents, sent only when the book is available to open. */ /** Server-approved book contents, sent only when the book is available to open. */
public record OpenDescriptiveBookScreenPayload(boolean fromStand, boolean offhand, GlobalPos source, ItemStack book) public record OpenDescriptiveBookScreenPayload(
boolean fromStand,
boolean offhand,
GlobalPos source,
ItemStack book,
@Nullable AgeTraits traits
)
implements CustomPacketPayload { implements CustomPacketPayload {
public static final Type<OpenDescriptiveBookScreenPayload> TYPE = new Type<>( public static final Type<OpenDescriptiveBookScreenPayload> TYPE = new Type<>(
ResourceLocation.fromNamespaceAndPath(Rivencraft.MOD_ID, "open_descriptive_book_screen") ResourceLocation.fromNamespaceAndPath(Rivencraft.MOD_ID, "open_descriptive_book_screen")
); );
public static final StreamCodec<RegistryFriendlyByteBuf, OpenDescriptiveBookScreenPayload> STREAM_CODEC = StreamCodec.composite( public static final StreamCodec<RegistryFriendlyByteBuf, OpenDescriptiveBookScreenPayload> STREAM_CODEC =
ByteBufCodecs.BOOL, OpenDescriptiveBookScreenPayload::fromStand, StreamCodec.of(OpenDescriptiveBookScreenPayload::encode, OpenDescriptiveBookScreenPayload::decode);
ByteBufCodecs.BOOL, OpenDescriptiveBookScreenPayload::offhand,
GlobalPos.STREAM_CODEC, OpenDescriptiveBookScreenPayload::source, private static void encode(RegistryFriendlyByteBuf buffer, OpenDescriptiveBookScreenPayload payload) {
ItemStack.STREAM_CODEC, OpenDescriptiveBookScreenPayload::book, buffer.writeBoolean(payload.fromStand());
OpenDescriptiveBookScreenPayload::new buffer.writeBoolean(payload.offhand());
); GlobalPos.STREAM_CODEC.encode(buffer, payload.source());
ItemStack.STREAM_CODEC.encode(buffer, payload.book());
buffer.writeBoolean(payload.traits() != null);
if (payload.traits() != null) {
AgeTraits traits = payload.traits();
buffer.writeEnum(traits.biomeDistribution());
buffer.writeEnum(traits.lighting());
buffer.writeEnum(traits.weather());
buffer.writeDouble(traits.celestialPeriod());
buffer.writeFloat(traits.celestialAngle());
buffer.writeFloat(traits.celestialPhase());
}
}
private static OpenDescriptiveBookScreenPayload decode(RegistryFriendlyByteBuf buffer) {
boolean fromStand = buffer.readBoolean();
boolean offhand = buffer.readBoolean();
GlobalPos source = GlobalPos.STREAM_CODEC.decode(buffer);
ItemStack book = ItemStack.STREAM_CODEC.decode(buffer);
AgeTraits traits = buffer.readBoolean()
? new AgeTraits(
buffer.readEnum(AgeTraits.BiomeDistribution.class),
buffer.readEnum(AgeTraits.Lighting.class),
buffer.readEnum(AgeTraits.Weather.class),
buffer.readDouble(),
buffer.readFloat(),
buffer.readFloat()
)
: null;
return new OpenDescriptiveBookScreenPayload(fromStand, offhand, source, book, traits);
}
@Override @Override
public Type<? extends CustomPacketPayload> type() { public Type<? extends CustomPacketPayload> type() {
@@ -29,6 +29,34 @@
"screen.rivencraft.age.name_prompt": "Name this Age before writing the Descriptive Book", "screen.rivencraft.age.name_prompt": "Name this Age before writing the Descriptive Book",
"screen.rivencraft.age.create": "Create", "screen.rivencraft.age.create": "Create",
"screen.rivencraft.age.generator": "Terrain: %s", "screen.rivencraft.age.generator": "Terrain: %s",
"screen.rivencraft.age.trait.biomes": "Biomes: %s",
"screen.rivencraft.age.trait.lighting": "Lighting: %s",
"screen.rivencraft.age.trait.weather": "Weather: %s",
"screen.rivencraft.age.trait.day_length": "Day length: %s",
"screen.rivencraft.age.trait.celestial_angle": "Angle: %s",
"screen.rivencraft.age.trait.celestial_phase": "Phase: %s",
"trait.rivencraft.biome_distribution.native": "Native",
"trait.rivencraft.biome_distribution.single": "Single",
"trait.rivencraft.biome_distribution.tiled": "Tiled",
"trait.rivencraft.biome_distribution.grid": "Grid",
"trait.rivencraft.biome_distribution.tiny": "Tiny",
"trait.rivencraft.biome_distribution.small": "Small",
"trait.rivencraft.biome_distribution.medium": "Medium",
"trait.rivencraft.biome_distribution.large": "Large",
"trait.rivencraft.biome_distribution.huge": "Huge",
"trait.rivencraft.lighting.normal": "Normal",
"trait.rivencraft.lighting.dark": "Dark",
"trait.rivencraft.lighting.bright": "Bright",
"trait.rivencraft.weather.normal": "Normal",
"trait.rivencraft.weather.fast": "Fast",
"trait.rivencraft.weather.slow": "Slow",
"trait.rivencraft.weather.off": "Off",
"trait.rivencraft.weather.always": "Always",
"trait.rivencraft.weather.cloudy": "Cloudy",
"trait.rivencraft.weather.rain": "Rain",
"trait.rivencraft.weather.snow": "Snow",
"trait.rivencraft.weather.storm": "Storm",
"trait.rivencraft.day_length.stopped": "Stopped",
"generator.rivencraft.standard": "Standard", "generator.rivencraft.standard": "Standard",
"generator.rivencraft.amplified": "Amplified", "generator.rivencraft.amplified": "Amplified",
"generator.rivencraft.island": "Island", "generator.rivencraft.island": "Island",