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

@@ -92,8 +92,8 @@ public final class ClientBookInteractionHandler {
return;
}
minecraft.setScreen(payload.fromStand()
? new DescriptiveBookScreen(payload.source().pos(), payload.book())
: new DescriptiveBookScreen(hand, payload.book()));
? new DescriptiveBookScreen(payload.source().pos(), payload.book(), payload.traits())
: new DescriptiveBookScreen(hand, payload.book(), payload.traits()));
});
}
}
@@ -1,6 +1,7 @@
package net.dnorberg.rivencraft.client.screen;
import net.dnorberg.rivencraft.book.AgeReference;
import net.dnorberg.rivencraft.book.AgeTraits;
import net.dnorberg.rivencraft.client.ClientBookExploreOverlay;
import net.dnorberg.rivencraft.network.UseDescriptiveBookPayload;
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.InteractionHand;
import net.neoforged.neoforge.network.PacketDistributor;
import org.jetbrains.annotations.Nullable;
import java.util.Locale;
public final class DescriptiveBookScreen extends Screen implements ExplorableBookScreen {
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 ResourceLocation generationProfile;
private final AgeTraits traits;
private final InteractionHand hand;
private final BlockPos bookStandPos;
private DestinationPreviewRenderer preview;
@@ -47,18 +52,20 @@ public final class DescriptiveBookScreen extends Screen implements ExplorableBoo
private Button exploreButton;
private boolean movingToExploreOverlay;
public DescriptiveBookScreen(InteractionHand hand, ItemStack book) {
public DescriptiveBookScreen(InteractionHand hand, ItemStack book, @Nullable AgeTraits traits) {
super(book.getHoverName());
age = book.get(ModDataComponents.AGE_REFERENCE.get());
generationProfile = book.get(ModDataComponents.AGE_GENERATION_PROFILE.get());
this.traits = traits;
this.hand = hand;
bookStandPos = null;
}
public DescriptiveBookScreen(BlockPos bookStandPos, ItemStack book) {
public DescriptiveBookScreen(BlockPos bookStandPos, ItemStack book, @Nullable AgeTraits traits) {
super(book.getHoverName());
age = book.get(ModDataComponents.AGE_REFERENCE.get());
generationProfile = book.get(ModDataComponents.AGE_GENERATION_PROFILE.get());
this.traits = traits;
hand = InteractionHand.MAIN_HAND;
this.bookStandPos = bookStandPos.immutable();
}
@@ -138,14 +145,34 @@ public final class DescriptiveBookScreen extends Screen implements ExplorableBoo
left + 177, top + 45, 124);
} else {
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) {
Component profileName = generationProfile.getNamespace().equals("rivencraft")
? Component.translatable("generator.rivencraft." + generationProfile.getPath())
: Component.literal(generationProfile.toString());
drawWrappedWithoutShadow(graphics,
detailsY = drawWrappedWithoutShadow(graphics,
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();
}
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)) {
graphics.drawString(font, line, x, y, BOOK_TEXT_COLOR, false);
y += font.lineHeight;
}
return y;
}
}
@@ -75,18 +75,20 @@ public final class ModNetworking {
return;
}
AgeReference reference = access.stack().get(ModDataComponents.AGE_REFERENCE.get());
if (reference != null && !access.stack().has(ModDataComponents.AGE_GENERATION_PROFILE.get())) {
AgeRegistrySavedData.get(player.serverLevel()).find(reference.id()).ifPresent(definition -> {
var definition = reference == null
? 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.sync().run();
});
}
// 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.
player.inventoryMenu.broadcastChanges();
PacketDistributor.sendToPlayer(player, new OpenDescriptiveBookScreenPayload(
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) {
@@ -1,27 +1,64 @@
package net.dnorberg.rivencraft.network;
import net.dnorberg.rivencraft.Rivencraft;
import net.dnorberg.rivencraft.book.AgeTraits;
import net.minecraft.core.GlobalPos;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.Nullable;
/** 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 {
public static final Type<OpenDescriptiveBookScreenPayload> TYPE = new Type<>(
ResourceLocation.fromNamespaceAndPath(Rivencraft.MOD_ID, "open_descriptive_book_screen")
);
public static final StreamCodec<RegistryFriendlyByteBuf, OpenDescriptiveBookScreenPayload> STREAM_CODEC = StreamCodec.composite(
ByteBufCodecs.BOOL, OpenDescriptiveBookScreenPayload::fromStand,
ByteBufCodecs.BOOL, OpenDescriptiveBookScreenPayload::offhand,
GlobalPos.STREAM_CODEC, OpenDescriptiveBookScreenPayload::source,
ItemStack.STREAM_CODEC, OpenDescriptiveBookScreenPayload::book,
OpenDescriptiveBookScreenPayload::new
);
public static final StreamCodec<RegistryFriendlyByteBuf, OpenDescriptiveBookScreenPayload> STREAM_CODEC =
StreamCodec.of(OpenDescriptiveBookScreenPayload::encode, OpenDescriptiveBookScreenPayload::decode);
private static void encode(RegistryFriendlyByteBuf buffer, OpenDescriptiveBookScreenPayload payload) {
buffer.writeBoolean(payload.fromStand());
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
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.create": "Create",
"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.amplified": "Amplified",
"generator.rivencraft.island": "Island",