Public Access
This commit is contained in:
1 parent
3109938a97
commit
2b916ccd0b
19 files changed
+437
-24
No files matched your search
@@ -12,11 +12,13 @@ been ported yet.
|
|||||||
|
|
||||||
## Linking Book
|
## Linking Book
|
||||||
|
|
||||||
Craft a Linking Book from a vanilla book and an ender pearl. Sneak-right-click
|
Craft a Linking Book from a vanilla book and an ender pearl, then use it in the
|
||||||
the unbound book to record the current location, then right-click it to return
|
air. An unbound book opens a screen where it can be named and bound to the
|
||||||
there. The destination is stored on the item and survives inventory and world
|
current location. A bound book opens its destination screen and travels only
|
||||||
saves. Travel only succeeds if the target dimension is loaded and a solid,
|
when the Link button is pressed. The destination is stored on the item and
|
||||||
collision-free landing position can be found nearby.
|
survives inventory and world saves. Travel only succeeds if the target
|
||||||
|
dimension is loaded and a solid, collision-free landing position can be found
|
||||||
|
nearby.
|
||||||
|
|
||||||
Right-click an empty vanilla lectern with a Rivencraft book to convert it into
|
Right-click an empty vanilla lectern with a Rivencraft book to convert it into
|
||||||
a Book Stand and place the book on it. Use a Linking Book on the stand by
|
a Book Stand and place the book on it. Use a Linking Book on the stand by
|
||||||
@@ -24,11 +26,16 @@ right-clicking with an empty hand. Sneak-right-click with an empty hand to take
|
|||||||
the book back and restore the vanilla lectern. Breaking a Book Stand drops both
|
the book back and restore the vanilla lectern. Breaking a Book Stand drops both
|
||||||
the stored book and a vanilla lectern.
|
the stored book and a vanilla lectern.
|
||||||
|
|
||||||
|
Version 0.1.4 establishes a Ponder-backed destination-preview boundary and an
|
||||||
|
animated loading view. Transmitting a destination snapshot and rendering its
|
||||||
|
actual blocks as the rotating scene is the next preview iteration.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- A Java 21 JDK
|
- A Java 21 JDK
|
||||||
- Minecraft 1.21.1
|
- Minecraft 1.21.1
|
||||||
- NeoForge 21.1.251 or newer in the 21.1 line
|
- NeoForge 21.1.251 or newer in the 21.1 line
|
||||||
|
- Ponder 1.0.82 for Minecraft 1.21.1 (client)
|
||||||
|
|
||||||
## Build and run
|
## Build and run
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -27,7 +27,7 @@ sourceSets.main.resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
// Add here additional repositories if required by some of the dependencies below.
|
maven { url = 'https://maven.createmod.net' }
|
||||||
}
|
}
|
||||||
|
|
||||||
base {
|
base {
|
||||||
@@ -116,6 +116,7 @@ configurations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation "net.createmod.ponder:ponder-neoforge:${ponder_version}+mc${minecraft_version}"
|
||||||
// Example optional mod dependency with JEI
|
// Example optional mod dependency with JEI
|
||||||
// The JEI API is declared for compile time use, while the full JEI artifact is used at runtime
|
// The JEI API is declared for compile time use, while the full JEI artifact is used at runtime
|
||||||
// compileOnly "mezz.jei:jei-${mc_version}-common-api:${jei_version}"
|
// compileOnly "mezz.jei:jei-${mc_version}-common-api:${jei_version}"
|
||||||
|
|||||||
@@ -48,6 +48,11 @@ the symbol grammar is introduced.
|
|||||||
- [ ] Return-link creation as part of Descriptive Book travel.
|
- [ ] Return-link creation as part of Descriptive Book travel.
|
||||||
- [x] Lectern-to-Book-Stand conversion, displayed book, activation, removal,
|
- [x] Lectern-to-Book-Stand conversion, displayed book, activation, removal,
|
||||||
persistence, and drops.
|
persistence, and drops.
|
||||||
|
- [x] Book-screen foundation with server-authoritative naming, binding, and
|
||||||
|
explicit link activation.
|
||||||
|
- [x] Ponder dependency and isolated destination-preview level boundary.
|
||||||
|
- [ ] Server-provided destination snapshots rendered as rotating Ponder scenes.
|
||||||
|
- [ ] Book Stand floating names and opening the stored book screen.
|
||||||
- [ ] Automated in-game tests for binding, persistence, and travel.
|
- [ ] Automated in-game tests for binding, persistence, and travel.
|
||||||
|
|
||||||
The temporary shapeless Linking Book recipe makes the linking subsystem easy
|
The temporary shapeless Linking Book recipe makes the linking subsystem easy
|
||||||
@@ -69,6 +74,17 @@ initial cooldown used Minecraft's item-type cooldown system, which delayed all
|
|||||||
Linking Book stacks at once. Version 0.1.3 removes that cooldown to match the
|
Linking Book stacks at once. Version 0.1.3 removes that cooldown to match the
|
||||||
legacy behavior.
|
legacy behavior.
|
||||||
|
|
||||||
|
Version 0.1.4 moves Linking Book binding and travel behind a book screen. The
|
||||||
|
client requests an action, while the server revalidates the held book and owns
|
||||||
|
all changes to its destination and custom name. Descriptive Book Age references
|
||||||
|
now carry a display name alongside their stable UUID, with backward-compatible
|
||||||
|
decoding for existing UUID-only data.
|
||||||
|
|
||||||
|
The preview code is separated behind a renderer interface and uses an isolated
|
||||||
|
Ponder level. The first iteration supplies the animated loading boundary; the
|
||||||
|
next iteration will add server-authoritative snapshot transport and populate
|
||||||
|
that level with the destination blocks.
|
||||||
|
|
||||||
## Version policy
|
## Version policy
|
||||||
|
|
||||||
Versions follow `x.y.z`:
|
Versions follow `x.y.z`:
|
||||||
|
|||||||
+2
-1
@@ -19,6 +19,7 @@ minecraft_version=1.21.1
|
|||||||
minecraft_version_range=[1.21.1]
|
minecraft_version_range=[1.21.1]
|
||||||
# The Neo version must agree with the Minecraft version to get a valid artifact
|
# The Neo version must agree with the Minecraft version to get a valid artifact
|
||||||
neo_version=21.1.251
|
neo_version=21.1.251
|
||||||
|
ponder_version=1.0.82
|
||||||
# The loader version range can only use the major version of FML as bounds
|
# The loader version range can only use the major version of FML as bounds
|
||||||
loader_version_range=[1,)
|
loader_version_range=[1,)
|
||||||
|
|
||||||
@@ -32,7 +33,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. See https://semver.org/
|
# The mod version. See https://semver.org/
|
||||||
mod_version=0.1.3
|
mod_version=0.1.4
|
||||||
# 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.
|
||||||
# See https://maven.apache.org/guides/mini/guide-naming-conventions.html
|
# See https://maven.apache.org/guides/mini/guide-naming-conventions.html
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import net.dnorberg.rivencraft.registry.ModItems;
|
|||||||
import net.dnorberg.rivencraft.registry.ModBlocks;
|
import net.dnorberg.rivencraft.registry.ModBlocks;
|
||||||
import net.dnorberg.rivencraft.registry.ModBlockEntities;
|
import net.dnorberg.rivencraft.registry.ModBlockEntities;
|
||||||
import net.dnorberg.rivencraft.event.BookStandConversionHandler;
|
import net.dnorberg.rivencraft.event.BookStandConversionHandler;
|
||||||
|
import net.dnorberg.rivencraft.network.ModNetworking;
|
||||||
import net.neoforged.bus.api.IEventBus;
|
import net.neoforged.bus.api.IEventBus;
|
||||||
import net.neoforged.fml.common.Mod;
|
import net.neoforged.fml.common.Mod;
|
||||||
import net.neoforged.neoforge.common.NeoForge;
|
import net.neoforged.neoforge.common.NeoForge;
|
||||||
@@ -18,6 +19,7 @@ public final class Rivencraft {
|
|||||||
public static final Logger LOGGER = LogUtils.getLogger();
|
public static final Logger LOGGER = LogUtils.getLogger();
|
||||||
|
|
||||||
public Rivencraft(IEventBus modEventBus) {
|
public Rivencraft(IEventBus modEventBus) {
|
||||||
|
modEventBus.addListener(ModNetworking::register);
|
||||||
ModDataComponents.register(modEventBus);
|
ModDataComponents.register(modEventBus);
|
||||||
ModBlocks.register(modEventBus);
|
ModBlocks.register(modEventBus);
|
||||||
ModBlockEntities.register(modEventBus);
|
ModBlockEntities.register(modEventBus);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package net.dnorberg.rivencraft.book;
|
package net.dnorberg.rivencraft.book;
|
||||||
|
|
||||||
import com.mojang.serialization.Codec;
|
import com.mojang.serialization.Codec;
|
||||||
|
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import net.minecraft.core.UUIDUtil;
|
import net.minecraft.core.UUIDUtil;
|
||||||
@@ -13,11 +14,21 @@ import net.minecraft.network.codec.StreamCodec;
|
|||||||
* Books only retain the identifier so dimension regeneration or migration does
|
* Books only retain the identifier so dimension regeneration or migration does
|
||||||
* not invalidate every existing book.</p>
|
* not invalidate every existing book.</p>
|
||||||
*/
|
*/
|
||||||
public record AgeReference(UUID id) {
|
public record AgeReference(UUID id, String name) {
|
||||||
public static final Codec<AgeReference> CODEC = UUIDUtil.STRING_CODEC.xmap(AgeReference::new, AgeReference::id);
|
private static final String UNNAMED_AGE = "Unnamed Age";
|
||||||
|
private static final Codec<AgeReference> RECORD_CODEC = RecordCodecBuilder.create(instance -> instance.group(
|
||||||
|
UUIDUtil.STRING_CODEC.fieldOf("id").forGetter(AgeReference::id),
|
||||||
|
Codec.STRING.optionalFieldOf("name", UNNAMED_AGE).forGetter(AgeReference::name)
|
||||||
|
).apply(instance, AgeReference::new));
|
||||||
|
public static final Codec<AgeReference> CODEC = Codec.withAlternative(
|
||||||
|
RECORD_CODEC,
|
||||||
|
UUIDUtil.STRING_CODEC.xmap(id -> new AgeReference(id, UNNAMED_AGE), AgeReference::id)
|
||||||
|
);
|
||||||
public static final StreamCodec<ByteBuf, AgeReference> STREAM_CODEC = StreamCodec.composite(
|
public static final StreamCodec<ByteBuf, AgeReference> STREAM_CODEC = StreamCodec.composite(
|
||||||
UUIDUtil.STREAM_CODEC,
|
UUIDUtil.STREAM_CODEC,
|
||||||
AgeReference::id,
|
AgeReference::id,
|
||||||
|
net.minecraft.network.codec.ByteBufCodecs.STRING_UTF8,
|
||||||
|
AgeReference::name,
|
||||||
AgeReference::new
|
AgeReference::new
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package net.dnorberg.rivencraft.client;
|
||||||
|
|
||||||
|
import net.dnorberg.rivencraft.client.screen.LinkingBookScreen;
|
||||||
|
import net.dnorberg.rivencraft.client.screen.DescriptiveBookScreen;
|
||||||
|
import net.dnorberg.rivencraft.registry.ModItems;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.world.InteractionResult;
|
||||||
|
import net.neoforged.neoforge.event.entity.player.PlayerInteractEvent;
|
||||||
|
|
||||||
|
public final class ClientBookInteractionHandler {
|
||||||
|
private ClientBookInteractionHandler() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void onRightClickItem(PlayerInteractEvent.RightClickItem event) {
|
||||||
|
boolean linkingBook = event.getItemStack().is(ModItems.LINKING_BOOK.get());
|
||||||
|
boolean descriptiveBook = event.getItemStack().is(ModItems.DESCRIPTIVE_BOOK.get());
|
||||||
|
if (!linkingBook && !descriptiveBook) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.setCanceled(true);
|
||||||
|
event.setCancellationResult(InteractionResult.SUCCESS);
|
||||||
|
if (linkingBook) {
|
||||||
|
Minecraft.getInstance().setScreen(new LinkingBookScreen(event.getHand(), event.getItemStack().copy()));
|
||||||
|
} else {
|
||||||
|
Minecraft.getInstance().setScreen(new DescriptiveBookScreen(event.getItemStack().copy()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,8 @@ import net.dnorberg.rivencraft.registry.ModBlockEntities;
|
|||||||
import net.neoforged.api.distmarker.Dist;
|
import net.neoforged.api.distmarker.Dist;
|
||||||
import net.neoforged.bus.api.SubscribeEvent;
|
import net.neoforged.bus.api.SubscribeEvent;
|
||||||
import net.neoforged.fml.common.EventBusSubscriber;
|
import net.neoforged.fml.common.EventBusSubscriber;
|
||||||
|
import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent;
|
||||||
|
import net.neoforged.neoforge.common.NeoForge;
|
||||||
import net.neoforged.neoforge.client.event.EntityRenderersEvent;
|
import net.neoforged.neoforge.client.event.EntityRenderersEvent;
|
||||||
|
|
||||||
@EventBusSubscriber(modid = Rivencraft.MOD_ID, value = Dist.CLIENT)
|
@EventBusSubscriber(modid = Rivencraft.MOD_ID, value = Dist.CLIENT)
|
||||||
@@ -16,4 +18,9 @@ public final class ClientEvents {
|
|||||||
public static void registerRenderers(EntityRenderersEvent.RegisterRenderers event) {
|
public static void registerRenderers(EntityRenderersEvent.RegisterRenderers event) {
|
||||||
event.registerBlockEntityRenderer(ModBlockEntities.BOOK_STAND.get(), BookStandRenderer::new);
|
event.registerBlockEntityRenderer(ModBlockEntities.BOOK_STAND.get(), BookStandRenderer::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SubscribeEvent
|
||||||
|
public static void clientSetup(FMLClientSetupEvent event) {
|
||||||
|
NeoForge.EVENT_BUS.addListener(ClientBookInteractionHandler::onRightClickItem);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package net.dnorberg.rivencraft.client.preview;
|
||||||
|
|
||||||
|
import net.minecraft.client.gui.GuiGraphics;
|
||||||
|
|
||||||
|
public interface DestinationPreviewRenderer extends AutoCloseable {
|
||||||
|
void tick();
|
||||||
|
|
||||||
|
void render(GuiGraphics graphics, int left, int top, int width, int height, float partialTick);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default void close() {
|
||||||
|
}
|
||||||
|
}
|
||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
package net.dnorberg.rivencraft.client.preview;
|
||||||
|
|
||||||
|
import net.createmod.ponder.api.level.PonderLevel;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.gui.GuiGraphics;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initial Ponder-backed preview boundary.
|
||||||
|
*
|
||||||
|
* <p>The isolated level is ready to receive server-provided snapshot blocks.
|
||||||
|
* Until snapshot networking lands, the panel renders an animated loading
|
||||||
|
* state rather than reading arbitrary remote chunks from the client.</p>
|
||||||
|
*/
|
||||||
|
public final class PonderDestinationPreviewRenderer implements DestinationPreviewRenderer {
|
||||||
|
private final PonderLevel previewLevel;
|
||||||
|
private int ticks;
|
||||||
|
|
||||||
|
public PonderDestinationPreviewRenderer() {
|
||||||
|
Minecraft minecraft = Minecraft.getInstance();
|
||||||
|
previewLevel = new PonderLevel(BlockPos.ZERO, minecraft.level);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tick() {
|
||||||
|
ticks++;
|
||||||
|
previewLevel.tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(GuiGraphics graphics, int left, int top, int width, int height, float partialTick) {
|
||||||
|
graphics.fill(left, top, left + width, top + height, 0xFF080C18);
|
||||||
|
graphics.renderOutline(left, top, width, height, 0xFF7B6AA8);
|
||||||
|
|
||||||
|
double angle = (ticks + partialTick) * 0.04;
|
||||||
|
int centerX = left + width / 2;
|
||||||
|
int centerY = top + height / 2;
|
||||||
|
int orbitX = centerX + (int)(Math.cos(angle) * Math.max(8, width / 4));
|
||||||
|
int orbitY = centerY + (int)(Math.sin(angle) * Math.max(4, height / 8));
|
||||||
|
graphics.fill(orbitX - 2, orbitY - 2, orbitX + 3, orbitY + 3, 0xFF8ED6FF);
|
||||||
|
graphics.drawCenteredString(
|
||||||
|
Minecraft.getInstance().font,
|
||||||
|
Component.translatable("screen.rivencraft.book.preview_pending"),
|
||||||
|
centerX,
|
||||||
|
centerY - 4,
|
||||||
|
0xFFB8C5E0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package net.dnorberg.rivencraft.client.screen;
|
||||||
|
|
||||||
|
import net.dnorberg.rivencraft.book.AgeReference;
|
||||||
|
import net.dnorberg.rivencraft.registry.ModDataComponents;
|
||||||
|
import net.minecraft.client.gui.GuiGraphics;
|
||||||
|
import net.minecraft.client.gui.components.Button;
|
||||||
|
import net.minecraft.client.gui.screens.Screen;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
|
||||||
|
public final class DescriptiveBookScreen extends Screen {
|
||||||
|
private final AgeReference age;
|
||||||
|
|
||||||
|
public DescriptiveBookScreen(ItemStack book) {
|
||||||
|
super(book.getHoverName());
|
||||||
|
age = book.get(ModDataComponents.AGE_REFERENCE.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void init() {
|
||||||
|
addRenderableWidget(Button.builder(Component.translatable("gui.done"), button -> onClose())
|
||||||
|
.bounds(width / 2 - 75, height / 2 + 55, 150, 20)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) {
|
||||||
|
super.render(graphics, mouseX, mouseY, partialTick);
|
||||||
|
int centerX = width / 2;
|
||||||
|
graphics.drawCenteredString(font, title, centerX, height / 2 - 70, 0xFFFFFFFF);
|
||||||
|
if (age == null) {
|
||||||
|
graphics.drawCenteredString(font, Component.translatable("screen.rivencraft.book.unwritten"),
|
||||||
|
centerX, height / 2 - 4, 0xFFAAAAAA);
|
||||||
|
} else {
|
||||||
|
graphics.drawCenteredString(font, Component.literal(age.name()), centerX, height / 2 - 16, 0xFFE7D7A9);
|
||||||
|
graphics.drawCenteredString(font, Component.literal(age.id().toString()), centerX, height / 2 + 4, 0xFF777777);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
package net.dnorberg.rivencraft.client.screen;
|
||||||
|
|
||||||
|
import net.dnorberg.rivencraft.book.LinkTarget;
|
||||||
|
import net.dnorberg.rivencraft.client.preview.DestinationPreviewRenderer;
|
||||||
|
import net.dnorberg.rivencraft.client.preview.PonderDestinationPreviewRenderer;
|
||||||
|
import net.dnorberg.rivencraft.network.BindLinkingBookPayload;
|
||||||
|
import net.dnorberg.rivencraft.network.ModNetworking;
|
||||||
|
import net.dnorberg.rivencraft.network.UseLinkingBookPayload;
|
||||||
|
import net.dnorberg.rivencraft.registry.ModDataComponents;
|
||||||
|
import net.minecraft.client.gui.GuiGraphics;
|
||||||
|
import net.minecraft.client.gui.components.Button;
|
||||||
|
import net.minecraft.client.gui.components.EditBox;
|
||||||
|
import net.minecraft.client.gui.screens.Screen;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.world.InteractionHand;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.neoforged.neoforge.network.PacketDistributor;
|
||||||
|
|
||||||
|
public final class LinkingBookScreen extends Screen {
|
||||||
|
private static final int PANEL_WIDTH = 180;
|
||||||
|
private static final int PREVIEW_HEIGHT = 100;
|
||||||
|
|
||||||
|
private final InteractionHand hand;
|
||||||
|
private final ItemStack book;
|
||||||
|
private final LinkTarget target;
|
||||||
|
private DestinationPreviewRenderer preview;
|
||||||
|
private EditBox titleInput;
|
||||||
|
|
||||||
|
public LinkingBookScreen(InteractionHand hand, ItemStack book) {
|
||||||
|
super(book.getHoverName());
|
||||||
|
this.hand = hand;
|
||||||
|
this.book = book;
|
||||||
|
target = book.get(ModDataComponents.LINK_TARGET.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void init() {
|
||||||
|
int left = (width - PANEL_WIDTH) / 2;
|
||||||
|
int buttonY = height / 2 + 72;
|
||||||
|
if (target == null) {
|
||||||
|
titleInput = new EditBox(font, left, height / 2 - 10, PANEL_WIDTH, 20,
|
||||||
|
Component.translatable("screen.rivencraft.book.title"));
|
||||||
|
titleInput.setMaxLength(ModNetworking.MAX_BOOK_TITLE_LENGTH);
|
||||||
|
titleInput.setHint(Component.translatable("screen.rivencraft.book.title_hint"));
|
||||||
|
addRenderableWidget(titleInput);
|
||||||
|
setInitialFocus(titleInput);
|
||||||
|
addRenderableWidget(Button.builder(Component.translatable("screen.rivencraft.book.bind"), button -> bind())
|
||||||
|
.bounds(left, height / 2 + 20, 86, 20)
|
||||||
|
.build());
|
||||||
|
addRenderableWidget(Button.builder(Component.translatable("gui.cancel"), button -> onClose())
|
||||||
|
.bounds(left + 94, height / 2 + 20, 86, 20)
|
||||||
|
.build());
|
||||||
|
} else {
|
||||||
|
preview = new PonderDestinationPreviewRenderer();
|
||||||
|
addRenderableWidget(Button.builder(Component.translatable("screen.rivencraft.book.link"), button -> link())
|
||||||
|
.bounds(left, buttonY, 86, 20)
|
||||||
|
.build());
|
||||||
|
addRenderableWidget(Button.builder(Component.translatable("gui.done"), button -> onClose())
|
||||||
|
.bounds(left + 94, buttonY, 86, 20)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tick() {
|
||||||
|
if (preview != null) {
|
||||||
|
preview.tick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) {
|
||||||
|
super.render(graphics, mouseX, mouseY, partialTick);
|
||||||
|
int centerX = width / 2;
|
||||||
|
graphics.drawCenteredString(font, title, centerX, height / 2 - 82, 0xFFFFFFFF);
|
||||||
|
if (target == null) {
|
||||||
|
graphics.drawCenteredString(font, Component.translatable("screen.rivencraft.book.name_prompt"),
|
||||||
|
centerX, height / 2 - 34, 0xFFB8B8B8);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int left = (width - PANEL_WIDTH) / 2;
|
||||||
|
int previewTop = height / 2 - 60;
|
||||||
|
preview.render(graphics, left, previewTop, PANEL_WIDTH, PREVIEW_HEIGHT, partialTick);
|
||||||
|
BlockPos pos = target.location().pos();
|
||||||
|
graphics.drawCenteredString(font, Component.literal(
|
||||||
|
target.location().dimension().location() + " " + pos.getX() + ", " + pos.getY() + ", " + pos.getZ()),
|
||||||
|
centerX, previewTop + PREVIEW_HEIGHT + 8, 0xFFB8B8B8);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removed() {
|
||||||
|
if (preview != null) {
|
||||||
|
preview.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bind() {
|
||||||
|
String name = titleInput.getValue().strip();
|
||||||
|
if (!name.isEmpty()) {
|
||||||
|
PacketDistributor.sendToServer(new BindLinkingBookPayload(hand == InteractionHand.OFF_HAND, name));
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void link() {
|
||||||
|
PacketDistributor.sendToServer(new UseLinkingBookPayload(hand == InteractionHand.OFF_HAND));
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,7 +37,7 @@ public final class DescriptiveBookItem extends Item {
|
|||||||
AgeReference age = stack.get(ModDataComponents.AGE_REFERENCE.get());
|
AgeReference age = stack.get(ModDataComponents.AGE_REFERENCE.get());
|
||||||
MutableComponent status = age == null
|
MutableComponent status = age == null
|
||||||
? Component.translatable("item.rivencraft.descriptive_book.unwritten")
|
? Component.translatable("item.rivencraft.descriptive_book.unwritten")
|
||||||
: Component.translatable("item.rivencraft.descriptive_book.age", age.shortId());
|
: Component.translatable("item.rivencraft.descriptive_book.age", age.name());
|
||||||
tooltipComponents.add(status.withStyle(ChatFormatting.GRAY));
|
tooltipComponents.add(status.withStyle(ChatFormatting.GRAY));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -33,19 +33,9 @@ public final class LinkingBookItem extends Item {
|
|||||||
|
|
||||||
LinkTarget target = stack.get(ModDataComponents.LINK_TARGET.get());
|
LinkTarget target = stack.get(ModDataComponents.LINK_TARGET.get());
|
||||||
if (target == null) {
|
if (target == null) {
|
||||||
if (!player.isShiftKeyDown()) {
|
return InteractionResultHolder.consume(stack);
|
||||||
player.displayClientMessage(Component.translatable("message.rivencraft.linking_book.bind_hint"), true);
|
|
||||||
return InteractionResultHolder.fail(stack);
|
|
||||||
}
|
|
||||||
|
|
||||||
target = LinkTarget.from(serverPlayer);
|
|
||||||
stack.set(ModDataComponents.LINK_TARGET.get(), target);
|
|
||||||
level.playSound(null, player.blockPosition(), SoundEvents.BOOK_PAGE_TURN, SoundSource.PLAYERS, 1.0F, 1.0F);
|
|
||||||
player.displayClientMessage(Component.translatable("message.rivencraft.linking_book.bound"), true);
|
|
||||||
return InteractionResultHolder.success(stack);
|
|
||||||
}
|
}
|
||||||
|
return InteractionResultHolder.consume(stack);
|
||||||
return useBoundBook(serverPlayer, stack);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static InteractionResultHolder<ItemStack> useBoundBook(ServerPlayer player, ItemStack stack) {
|
public static InteractionResultHolder<ItemStack> useBoundBook(ServerPlayer player, ItemStack stack) {
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package net.dnorberg.rivencraft.network;
|
||||||
|
|
||||||
|
import net.dnorberg.rivencraft.Rivencraft;
|
||||||
|
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;
|
||||||
|
|
||||||
|
public record BindLinkingBookPayload(boolean offhand, String title) implements CustomPacketPayload {
|
||||||
|
public static final Type<BindLinkingBookPayload> TYPE = new Type<>(
|
||||||
|
ResourceLocation.fromNamespaceAndPath(Rivencraft.MOD_ID, "bind_linking_book")
|
||||||
|
);
|
||||||
|
public static final StreamCodec<RegistryFriendlyByteBuf, BindLinkingBookPayload> STREAM_CODEC = StreamCodec.composite(
|
||||||
|
ByteBufCodecs.BOOL,
|
||||||
|
BindLinkingBookPayload::offhand,
|
||||||
|
ByteBufCodecs.STRING_UTF8,
|
||||||
|
BindLinkingBookPayload::title,
|
||||||
|
BindLinkingBookPayload::new
|
||||||
|
);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Type<? extends CustomPacketPayload> type() {
|
||||||
|
return TYPE;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package net.dnorberg.rivencraft.network;
|
||||||
|
|
||||||
|
import net.dnorberg.rivencraft.book.LinkTarget;
|
||||||
|
import net.dnorberg.rivencraft.item.LinkingBookItem;
|
||||||
|
import net.dnorberg.rivencraft.registry.ModDataComponents;
|
||||||
|
import net.dnorberg.rivencraft.registry.ModItems;
|
||||||
|
import net.minecraft.core.component.DataComponents;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
|
import net.minecraft.sounds.SoundEvents;
|
||||||
|
import net.minecraft.sounds.SoundSource;
|
||||||
|
import net.minecraft.world.InteractionHand;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent;
|
||||||
|
import net.neoforged.neoforge.network.handling.IPayloadContext;
|
||||||
|
import net.neoforged.neoforge.network.registration.PayloadRegistrar;
|
||||||
|
|
||||||
|
public final class ModNetworking {
|
||||||
|
public static final int MAX_BOOK_TITLE_LENGTH = 32;
|
||||||
|
|
||||||
|
private ModNetworking() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void register(RegisterPayloadHandlersEvent event) {
|
||||||
|
PayloadRegistrar registrar = event.registrar("1");
|
||||||
|
registrar.playToServer(BindLinkingBookPayload.TYPE, BindLinkingBookPayload.STREAM_CODEC, ModNetworking::bindBook);
|
||||||
|
registrar.playToServer(UseLinkingBookPayload.TYPE, UseLinkingBookPayload.STREAM_CODEC, ModNetworking::useBook);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void bindBook(BindLinkingBookPayload payload, IPayloadContext context) {
|
||||||
|
if (!(context.player() instanceof ServerPlayer player)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemStack stack = player.getItemInHand(hand(payload.offhand()));
|
||||||
|
if (!stack.is(ModItems.LINKING_BOOK.get()) || stack.has(ModDataComponents.LINK_TARGET.get())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String title = normalizeTitle(payload.title());
|
||||||
|
if (title.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stack.set(ModDataComponents.LINK_TARGET.get(), LinkTarget.from(player));
|
||||||
|
stack.set(DataComponents.CUSTOM_NAME, Component.literal(title));
|
||||||
|
player.level().playSound(null, player.blockPosition(), SoundEvents.BOOK_PAGE_TURN, SoundSource.PLAYERS, 1.0F, 1.0F);
|
||||||
|
player.displayClientMessage(Component.translatable("message.rivencraft.linking_book.bound", title), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void useBook(UseLinkingBookPayload payload, IPayloadContext context) {
|
||||||
|
if (!(context.player() instanceof ServerPlayer player)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemStack stack = player.getItemInHand(hand(payload.offhand()));
|
||||||
|
if (stack.is(ModItems.LINKING_BOOK.get()) && stack.has(ModDataComponents.LINK_TARGET.get())) {
|
||||||
|
LinkingBookItem.useBoundBook(player, stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static InteractionHand hand(boolean offhand) {
|
||||||
|
return offhand ? InteractionHand.OFF_HAND : InteractionHand.MAIN_HAND;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String normalizeTitle(String title) {
|
||||||
|
String normalized = title.strip();
|
||||||
|
return normalized.length() <= MAX_BOOK_TITLE_LENGTH
|
||||||
|
? normalized
|
||||||
|
: normalized.substring(0, MAX_BOOK_TITLE_LENGTH).stripTrailing();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package net.dnorberg.rivencraft.network;
|
||||||
|
|
||||||
|
import net.dnorberg.rivencraft.Rivencraft;
|
||||||
|
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;
|
||||||
|
|
||||||
|
public record UseLinkingBookPayload(boolean offhand) implements CustomPacketPayload {
|
||||||
|
public static final Type<UseLinkingBookPayload> TYPE = new Type<>(
|
||||||
|
ResourceLocation.fromNamespaceAndPath(Rivencraft.MOD_ID, "use_linking_book")
|
||||||
|
);
|
||||||
|
public static final StreamCodec<RegistryFriendlyByteBuf, UseLinkingBookPayload> STREAM_CODEC = StreamCodec.composite(
|
||||||
|
ByteBufCodecs.BOOL,
|
||||||
|
UseLinkingBookPayload::offhand,
|
||||||
|
UseLinkingBookPayload::new
|
||||||
|
);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Type<? extends CustomPacketPayload> type() {
|
||||||
|
return TYPE;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,22 @@
|
|||||||
{
|
{
|
||||||
"item.rivencraft.descriptive_book": "Descriptive Book",
|
"item.rivencraft.descriptive_book": "Descriptive Book",
|
||||||
"item.rivencraft.descriptive_book.unwritten": "Unwritten",
|
"item.rivencraft.descriptive_book.unwritten": "Unwritten",
|
||||||
"item.rivencraft.descriptive_book.age": "Age %s",
|
"item.rivencraft.descriptive_book.age": "%s",
|
||||||
"item.rivencraft.linking_book": "Linking Book",
|
"item.rivencraft.linking_book": "Linking Book",
|
||||||
"item.rivencraft.linking_book.unbound": "Unbound — sneak-use to bind",
|
"item.rivencraft.linking_book.unbound": "Unbound — sneak-use to bind",
|
||||||
"item.rivencraft.linking_book.destination": "%s (%s, %s, %s)",
|
"item.rivencraft.linking_book.destination": "%s (%s, %s, %s)",
|
||||||
"message.rivencraft.linking_book.bind_hint": "Sneak-use to bind this book to your current location",
|
"message.rivencraft.linking_book.bind_hint": "Sneak-use to bind this book to your current location",
|
||||||
"message.rivencraft.linking_book.unbound": "This Linking Book is unbound",
|
"message.rivencraft.linking_book.unbound": "This Linking Book is unbound",
|
||||||
"message.rivencraft.linking_book.bound": "Linking Book bound",
|
"message.rivencraft.linking_book.bound": "Linking Book bound as %s",
|
||||||
"message.rivencraft.linking_book.linked": "Linked",
|
"message.rivencraft.linking_book.linked": "Linked",
|
||||||
"message.rivencraft.linking_book.missing_dimension": "That dimension is unavailable",
|
"message.rivencraft.linking_book.missing_dimension": "That dimension is unavailable",
|
||||||
"message.rivencraft.linking_book.unsafe": "No safe destination was found near the link",
|
"message.rivencraft.linking_book.unsafe": "No safe destination was found near the link",
|
||||||
|
"screen.rivencraft.book.title": "Book title",
|
||||||
|
"screen.rivencraft.book.title_hint": "Name this Linking Book",
|
||||||
|
"screen.rivencraft.book.name_prompt": "Name the book before binding it to this location",
|
||||||
|
"screen.rivencraft.book.bind": "Bind",
|
||||||
|
"screen.rivencraft.book.link": "Link",
|
||||||
|
"screen.rivencraft.book.unwritten": "This Descriptive Book is unwritten",
|
||||||
|
"screen.rivencraft.book.preview_pending": "Ponder preview loading",
|
||||||
"itemGroup.rivencraft": "Rivencraft"
|
"itemGroup.rivencraft": "Rivencraft"
|
||||||
}
|
}
|
||||||
@@ -86,6 +86,13 @@ A spiritual successor and modern port of Mystcraft for Minecraft 1.21.1 on NeoFo
|
|||||||
ordering="NONE"
|
ordering="NONE"
|
||||||
side="BOTH"
|
side="BOTH"
|
||||||
|
|
||||||
|
[[dependencies.${mod_id}]]
|
||||||
|
modId="ponder"
|
||||||
|
type="required"
|
||||||
|
versionRange="[1.0.82,2.0.0)"
|
||||||
|
ordering="AFTER"
|
||||||
|
side="CLIENT"
|
||||||
|
|
||||||
# Features are specific properties of the game environment, that you may want to declare you require. This example declares
|
# Features are specific properties of the game environment, that you may want to declare you require. This example declares
|
||||||
# that your mod requires GL version 3.2 or higher. Other features will be added. They are side aware so declaring this won't
|
# that your mod requires GL version 3.2 or higher. Other features will be added. They are side aware so declaring this won't
|
||||||
# stop your mod loading on the server for example.
|
# stop your mod loading on the server for example.
|
||||||
|
|||||||
Reference in new issue
Block a user