Public Access
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56005ac512 | ||
|
|
9ff931afa9 |
No files matched your search
@@ -0,0 +1,37 @@
|
||||
# Rivencraft 1.0.1.3-alpha.1 — Minecraft 26.3
|
||||
|
||||
Experimental **NeoForge 26.3** port preview. This is separate from the
|
||||
supported Minecraft 1.21.1 release and does **not** have feature parity. Use
|
||||
a disposable test world or make a backup first. Do not migrate a 1.21.1
|
||||
Rivencraft world to this alpha.
|
||||
|
||||
## Changes since 1.0.1.2-alpha.1
|
||||
|
||||
- An unbound Descriptive Book now opens a naming screen. Binding it creates a
|
||||
distinct prototype Age with a random seed; duplicate names receive a
|
||||
numbered suffix.
|
||||
- A bound Descriptive Book opens a book screen whose destination panel can be
|
||||
clicked to enter its Age. The server validates the held book and waits for
|
||||
the client's Age catalog acknowledgement before travel.
|
||||
- Retired Age IDs are excluded when assigning a new book's Age name, so an
|
||||
archived world cannot be silently reused.
|
||||
|
||||
## Still experimental
|
||||
|
||||
These Ages use prototype vanilla terrain. Page-authored generation, crafting
|
||||
and workstations, most items and blocks, portals, Link modifiers, instability,
|
||||
live destination previews, and optional mod integrations are not ported. The
|
||||
book panel is a placeholder, not a rendered destination view. The
|
||||
[port status](MINECRAFT_26_3_PORT.md) and [open port issues](https://git.paddedcell.org/dnorberg/rivencraft/issues)
|
||||
track the remaining work. The stable 1.21.1 release remains available
|
||||
separately.
|
||||
|
||||
## Verification
|
||||
|
||||
- Clean 26.3 Gradle build passed.
|
||||
- Disposable dedicated-server smoke test passed: two distinct Age seeds and
|
||||
their terrain persisted across restart.
|
||||
- The player verified Descriptive Book binding and travel in game on
|
||||
2026-10-29.
|
||||
|
||||
These checks are **not** the full port acceptance matrix or a stability claim.
|
||||
@@ -374,6 +374,51 @@ prototype vanilla terrain, normal authoring and most content are absent, and
|
||||
worlds should be disposable or backed up. The 1.21.1 stable release remains
|
||||
independent; issues #96–#104 stay open until the full port and validation pass.
|
||||
|
||||
The next development slice, `1.0.1.3-alpha.1`, adds a basic Descriptive Book
|
||||
screen and server-validated binding. Naming an unbound book creates a unique
|
||||
prototype Age with a random seed; clicking the bound book's placeholder panel
|
||||
uses the acknowledged-catalog travel gate. This is not authored Page binding
|
||||
or a live destination preview. The player confirmed the Descriptive Book
|
||||
binding and travel interaction in game on 2026-10-29.
|
||||
|
||||
The following `1.0.1.4-alpha.1` development iteration registers a small
|
||||
Rivencraft creative tab for the two Books, Crystal Block, and Crystal Shard.
|
||||
It builds and passes the dedicated-server Age smoke test. The player confirmed
|
||||
the tab and four entries in game on 2026-10-29. No Page categories or
|
||||
unported items are advertised in the 26.3 tab.
|
||||
|
||||
The next local `1.0.1.5-alpha.1` iteration selects Legacy's unwritten
|
||||
Linking Book sprite until a target is bound, then the bound sprite. It also
|
||||
restores the four-Crystal-Shard crafting recipe for a Crystal Block. Both
|
||||
changes passed the player's in-game visual/crafting check on 2026-10-29.
|
||||
|
||||
The local `1.0.1.6-alpha.1` iteration ports Budding Crystal and the four
|
||||
blue crystal growth stages, reusing the 1.21.1 sprites and vanilla amethyst
|
||||
growth/harvest rules. Crystal clusters yield Shards, but natural crystal
|
||||
formation generation remains unported. Test growth with a Creative or
|
||||
command-placed Budding Crystal; the player passed the in-game check.
|
||||
|
||||
The local `1.0.1.7-alpha.1` iteration adds one persistent Page item with
|
||||
unwritten and Link Panel variants. It reuses the existing Page/Link Panel
|
||||
artwork and exposes only the Link Panel variant in its own creative tab,
|
||||
matching the 1.21.1 separation of Link Pages from symbol pages. Blank Pages
|
||||
remain command-only bases; the Ink Mixer, Book Binder and symbol writing are
|
||||
not yet ported. The player passed the in-game visual and persistence check.
|
||||
|
||||
The local `1.0.1.8-alpha.1` iteration ports the Ink Vial item, its animated
|
||||
sprite, and the black-dye/glass-bottle/water-bucket crafting recipe. Using
|
||||
it with paper in the other hand prepares a Link Panel at the current 1.21.1
|
||||
default cost of 2 ink from a 16-use vial. Ink remaining persists per vial;
|
||||
an empty vial yields a glass bottle. The Ink Mixer, configurable ink cost,
|
||||
and Book Binder remain unported. The player passed the in-game craft/use check.
|
||||
|
||||
The local `1.0.1.9-alpha.1` iteration ports the special shapeless leather +
|
||||
Link Panel recipe for an unwritten Linking Book. The 26.3 recipe serializer
|
||||
rejects blank Pages and extra ingredients and preserves a panel's custom name
|
||||
without carrying its Page identity onto the Book. No Link Panel modifiers are
|
||||
ported yet. The player passed the in-game crafting check on 2026-10-29. The
|
||||
full Book Binder GUI and Age authoring remain the next larger port step.
|
||||
|
||||
## Verification of this first slice
|
||||
|
||||
The 1.21.1 `compileJava` and `build` tasks passed offline after the backend
|
||||
|
||||
+23
-2
@@ -3,12 +3,33 @@
|
||||
This is an **experimental alpha port** for issues #97–#104, not gameplay parity
|
||||
with the Minecraft 1.21.1 release. It registers basic items and a runtime Age
|
||||
prototype with persisted seeds. The operator-only `rivencraft_port age`
|
||||
commands create and inspect separate vanilla-terrain levels. Descriptive Book
|
||||
travel and named Linking Book binding/travel work as limited probes. Authored
|
||||
commands create and inspect separate vanilla-terrain levels. The current
|
||||
development slice can name an unbound Descriptive Book to create a prototype
|
||||
Age, then enter it from the book GUI; the player verified this interaction in game.
|
||||
Named Linking Book binding/travel works as a limited probe. Authored
|
||||
generation, most content, recipes, workstations, portal systems, modifiers and
|
||||
the destination preview renderer remain unported. Use a test world and keep a
|
||||
backup; do not substitute this JAR for the 1.21.1 mod or import 1.21.1 saves.
|
||||
|
||||
The creative tab contains the two Books, Crystal Block and Crystal Shard;
|
||||
the player verified these in game. Unbound Linking Books use the Legacy
|
||||
sprite, and four Crystal Shards craft into a Crystal Block; both were also
|
||||
verified in game. The current local iteration adds Budding Crystal and all
|
||||
four blue crystal growth stages with vanilla-style harvesting. Natural crystal
|
||||
formation generation is not yet ported; use Creative or commands to obtain a
|
||||
Budding Crystal for this test. The player verified the growth chain in game.
|
||||
The next local slice adds the Link Panel Page as a separate creative entry,
|
||||
using the existing item texture. Blank Pages remain unavailable in Creative;
|
||||
the player verified the Link Panel's appearance in game. Ink Vials now use
|
||||
the existing crafting recipe and animated sprite. With paper in the other
|
||||
hand, an Ink Vial prepares a Link Panel for two of its sixteen ink uses, as
|
||||
in the current 1.21.1 default. The Book Binder and symbol-writing systems
|
||||
are still unported. The player verified the Ink Vial interaction in game.
|
||||
A prepared Link Panel can now be combined shapelessly with leather to craft
|
||||
an unwritten Linking Book, as in 1.21.1. This custom recipe rejects a blank
|
||||
Page and extra ingredients. The Book Binder remains the next larger step for
|
||||
assembling Descriptive Books from authored pages.
|
||||
|
||||
From the repository root:
|
||||
|
||||
```sh
|
||||
|
||||
@@ -63,10 +63,17 @@ tasks.named('processResources', ProcessResources).configure {
|
||||
// Reuse the existing sprite and base model without copying binary assets.
|
||||
from('../../src/main/resources') {
|
||||
include 'assets/rivencraft/textures/item/crystal_shard.png'
|
||||
include 'assets/rivencraft/textures/item/page.png'
|
||||
include 'assets/rivencraft/textures/item/page_link_panel.png'
|
||||
include 'assets/rivencraft/models/item/page_link_panel.json'
|
||||
include 'assets/rivencraft/textures/item/ink_vial.png'
|
||||
include 'assets/rivencraft/textures/item/ink_vial.png.mcmeta'
|
||||
include 'assets/rivencraft/models/item/ink_vial.json'
|
||||
include 'assets/rivencraft/models/item/crystal_shard.json'
|
||||
include 'assets/rivencraft/textures/item/descriptive_book.png'
|
||||
include 'assets/rivencraft/models/item/descriptive_book.json'
|
||||
include 'assets/rivencraft/textures/item/linking_book.png'
|
||||
include 'assets/rivencraft/textures/item/linking_book_unwritten.png'
|
||||
include 'assets/rivencraft/models/item/linking_book_bound.json'
|
||||
include 'assets/rivencraft/textures/gui/bookui_cover.png'
|
||||
include 'assets/rivencraft/textures/gui/notebook_pages.png'
|
||||
@@ -74,7 +81,28 @@ tasks.named('processResources', ProcessResources).configure {
|
||||
include 'assets/rivencraft/textures/block/crystal_block.png'
|
||||
include 'assets/rivencraft/models/block/crystal_block.json'
|
||||
include 'assets/rivencraft/blockstates/crystal_block.json'
|
||||
include 'assets/rivencraft/blockstates/budding_crystal.json'
|
||||
include 'assets/rivencraft/blockstates/small_crystal_bud.json'
|
||||
include 'assets/rivencraft/blockstates/medium_crystal_bud.json'
|
||||
include 'assets/rivencraft/blockstates/large_crystal_bud.json'
|
||||
include 'assets/rivencraft/blockstates/crystal_cluster.json'
|
||||
include 'assets/rivencraft/models/block/budding_crystal.json'
|
||||
include 'assets/rivencraft/models/block/small_crystal_bud.json'
|
||||
include 'assets/rivencraft/models/block/medium_crystal_bud.json'
|
||||
include 'assets/rivencraft/models/block/large_crystal_bud.json'
|
||||
include 'assets/rivencraft/models/block/crystal_cluster.json'
|
||||
include 'assets/rivencraft/models/item/budding_crystal.json'
|
||||
include 'assets/rivencraft/models/item/small_crystal_bud.json'
|
||||
include 'assets/rivencraft/models/item/medium_crystal_bud.json'
|
||||
include 'assets/rivencraft/models/item/large_crystal_bud.json'
|
||||
include 'assets/rivencraft/models/item/crystal_cluster.json'
|
||||
include 'assets/rivencraft/textures/block/small_crystal_bud.png'
|
||||
include 'assets/rivencraft/textures/block/medium_crystal_bud.png'
|
||||
include 'assets/rivencraft/textures/block/large_crystal_bud.png'
|
||||
include 'assets/rivencraft/textures/block/crystal_cluster.png'
|
||||
include 'data/rivencraft/loot_table/blocks/crystal_block.json'
|
||||
include 'data/minecraft/tags/block/mineable/pickaxe.json'
|
||||
include 'data/minecraft/tags/item/cluster_max_harvestables.json'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,4 +7,4 @@ neo_version=26.3.0.99
|
||||
mod_id=rivencraft
|
||||
mod_name=Rivencraft
|
||||
mod_license=LGPL-3.0-or-later
|
||||
mod_version=1.0.1.2-alpha.1
|
||||
mod_version=1.0.1.9-alpha.1
|
||||
@@ -1,21 +1,31 @@
|
||||
package net.dnorberg.rivencraft;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import com.mojang.serialization.Codec;
|
||||
import net.dnorberg.rivencraft.age.RuntimeAgeBackend;
|
||||
import net.dnorberg.rivencraft.age.PortAgeCommands;
|
||||
import net.dnorberg.rivencraft.age.AgeCatalogSync;
|
||||
import net.dnorberg.rivencraft.block.BuddingCrystalBlock;
|
||||
import net.dnorberg.rivencraft.item.DescriptiveBookItem;
|
||||
import net.dnorberg.rivencraft.item.LinkBookTarget;
|
||||
import net.dnorberg.rivencraft.item.InkVialItem;
|
||||
import net.dnorberg.rivencraft.item.LinkingBookItem;
|
||||
import net.dnorberg.rivencraft.item.PageItem;
|
||||
import net.dnorberg.rivencraft.network.PortBookNetworking;
|
||||
import net.dnorberg.rivencraft.registry.ModCreativeTabs;
|
||||
import net.dnorberg.rivencraft.writing.LinkingBookRecipe;
|
||||
import net.minecraft.core.component.DataComponentType;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.network.codec.ByteBufCodecs;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.AmethystClusterBlock;
|
||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||
import net.minecraft.world.level.material.MapColor;
|
||||
import net.minecraft.world.item.BlockItem;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.crafting.RecipeSerializer;
|
||||
import net.neoforged.bus.api.IEventBus;
|
||||
import net.neoforged.fml.common.Mod;
|
||||
import net.neoforged.neoforge.registries.DeferredBlock;
|
||||
@@ -32,6 +42,8 @@ public final class Rivencraft {
|
||||
public static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static final DeferredRegister.Blocks BLOCKS = DeferredRegister.createBlocks(MOD_ID);
|
||||
private static final DeferredRegister.Items ITEMS = DeferredRegister.createItems(MOD_ID);
|
||||
private static final DeferredRegister<RecipeSerializer<?>> RECIPE_SERIALIZERS =
|
||||
DeferredRegister.create(Registries.RECIPE_SERIALIZER, MOD_ID);
|
||||
private static final DeferredRegister.DataComponents COMPONENTS =
|
||||
DeferredRegister.createDataComponents(Registries.DATA_COMPONENT_TYPE, MOD_ID);
|
||||
|
||||
@@ -41,20 +53,54 @@ public final class Rivencraft {
|
||||
public static final Supplier<DataComponentType<LinkBookTarget>> LINK_BOOK_TARGET = COMPONENTS.registerComponentType(
|
||||
"link_book_target", builder -> builder.persistent(LinkBookTarget.CODEC)
|
||||
.networkSynchronized(LinkBookTarget.STREAM_CODEC));
|
||||
/** A present component distinguishes Link Panels from unwritten base Pages. */
|
||||
public static final Supplier<DataComponentType<Boolean>> LINK_PANEL_PAGE = COMPONENTS.registerComponentType(
|
||||
"link_panel_page", builder -> builder.persistent(Codec.BOOL)
|
||||
.networkSynchronized(ByteBufCodecs.BOOL));
|
||||
public static final Supplier<DataComponentType<Integer>> INK_REMAINING = COMPONENTS.registerComponentType(
|
||||
"ink_remaining", builder -> builder.persistent(Codec.INT)
|
||||
.networkSynchronized(ByteBufCodecs.VAR_INT));
|
||||
|
||||
public static final DeferredBlock<Block> CRYSTAL_BLOCK = BLOCKS.registerBlock("crystal_block",
|
||||
Block::new, () -> BlockBehaviour.Properties.ofFullCopy(Blocks.AMETHYST_BLOCK));
|
||||
public static final DeferredItem<BlockItem> CRYSTAL_BLOCK_ITEM = ITEMS.registerSimpleBlockItem(CRYSTAL_BLOCK);
|
||||
public static final DeferredBlock<BuddingCrystalBlock> BUDDING_CRYSTAL = BLOCKS.registerBlock(
|
||||
"budding_crystal", BuddingCrystalBlock::new,
|
||||
() -> BlockBehaviour.Properties.ofFullCopy(Blocks.BUDDING_AMETHYST).mapColor(MapColor.COLOR_BLUE));
|
||||
public static final DeferredItem<BlockItem> BUDDING_CRYSTAL_ITEM = ITEMS.registerSimpleBlockItem(BUDDING_CRYSTAL);
|
||||
public static final DeferredBlock<AmethystClusterBlock> SMALL_CRYSTAL_BUD = BLOCKS.registerBlock(
|
||||
"small_crystal_bud", properties -> new AmethystClusterBlock(3.0F, 4.0F, properties),
|
||||
() -> BlockBehaviour.Properties.ofFullCopy(Blocks.SMALL_AMETHYST_BUD));
|
||||
public static final DeferredItem<BlockItem> SMALL_CRYSTAL_BUD_ITEM = ITEMS.registerSimpleBlockItem(SMALL_CRYSTAL_BUD);
|
||||
public static final DeferredBlock<AmethystClusterBlock> MEDIUM_CRYSTAL_BUD = BLOCKS.registerBlock(
|
||||
"medium_crystal_bud", properties -> new AmethystClusterBlock(4.0F, 3.0F, properties),
|
||||
() -> BlockBehaviour.Properties.ofFullCopy(Blocks.MEDIUM_AMETHYST_BUD));
|
||||
public static final DeferredItem<BlockItem> MEDIUM_CRYSTAL_BUD_ITEM = ITEMS.registerSimpleBlockItem(MEDIUM_CRYSTAL_BUD);
|
||||
public static final DeferredBlock<AmethystClusterBlock> LARGE_CRYSTAL_BUD = BLOCKS.registerBlock(
|
||||
"large_crystal_bud", properties -> new AmethystClusterBlock(5.0F, 3.0F, properties),
|
||||
() -> BlockBehaviour.Properties.ofFullCopy(Blocks.LARGE_AMETHYST_BUD));
|
||||
public static final DeferredItem<BlockItem> LARGE_CRYSTAL_BUD_ITEM = ITEMS.registerSimpleBlockItem(LARGE_CRYSTAL_BUD);
|
||||
public static final DeferredBlock<AmethystClusterBlock> CRYSTAL_CLUSTER = BLOCKS.registerBlock(
|
||||
"crystal_cluster", properties -> new AmethystClusterBlock(7.0F, 3.0F, properties),
|
||||
() -> BlockBehaviour.Properties.ofFullCopy(Blocks.AMETHYST_CLUSTER));
|
||||
public static final DeferredItem<BlockItem> CRYSTAL_CLUSTER_ITEM = ITEMS.registerSimpleBlockItem(CRYSTAL_CLUSTER);
|
||||
public static final DeferredItem<Item> CRYSTAL_SHARD = ITEMS.registerSimpleItem("crystal_shard");
|
||||
public static final DeferredItem<PageItem> PAGE = ITEMS.registerItem("page", PageItem::new);
|
||||
public static final DeferredItem<InkVialItem> INK_VIAL = ITEMS.registerItem(
|
||||
"ink_vial", InkVialItem::new, properties -> properties.stacksTo(16));
|
||||
public static final DeferredItem<DescriptiveBookItem> DESCRIPTIVE_BOOK = ITEMS.registerItem(
|
||||
"descriptive_book", DescriptiveBookItem::new, properties -> properties.stacksTo(1));
|
||||
public static final DeferredItem<LinkingBookItem> LINKING_BOOK = ITEMS.registerItem(
|
||||
"linking_book", LinkingBookItem::new, properties -> properties.stacksTo(1));
|
||||
public static final Supplier<RecipeSerializer<LinkingBookRecipe>> LINKING_BOOK_RECIPE =
|
||||
RECIPE_SERIALIZERS.register("linking_book", () -> LinkingBookRecipe.SERIALIZER);
|
||||
|
||||
public Rivencraft(IEventBus modEventBus) {
|
||||
BLOCKS.register(modEventBus);
|
||||
ITEMS.register(modEventBus);
|
||||
COMPONENTS.register(modEventBus);
|
||||
RECIPE_SERIALIZERS.register(modEventBus);
|
||||
ModCreativeTabs.register(modEventBus);
|
||||
NeoForge.EVENT_BUS.addListener(RuntimeAgeBackend::onLevelLoad);
|
||||
NeoForge.EVENT_BUS.addListener(PortAgeCommands::register);
|
||||
NeoForge.EVENT_BUS.addListener(AgeCatalogSync::onLogin);
|
||||
|
||||
@@ -153,6 +153,13 @@ public final class RuntimeAgeBackend {
|
||||
return index(server).entries();
|
||||
}
|
||||
|
||||
/** Includes retired IDs, which must never be silently reused by a new book. */
|
||||
public static boolean isNameTaken(MinecraftServer server, Identifier id) {
|
||||
checkServerThread(server);
|
||||
ageName(id);
|
||||
return index(server).contains(id);
|
||||
}
|
||||
|
||||
private static AgeLevelIndex index(MinecraftServer server) {
|
||||
return server.overworld().getDataStorage().computeIfAbsent(AgeLevelIndex.TYPE);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package net.dnorberg.rivencraft.block;
|
||||
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.level.block.AmethystBlock;
|
||||
import net.minecraft.world.level.block.AmethystClusterBlock;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.BuddingAmethystBlock;
|
||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.material.Fluids;
|
||||
|
||||
/** Vanilla amethyst growth, using Rivencraft's blue crystal stages. */
|
||||
public final class BuddingCrystalBlock extends AmethystBlock {
|
||||
public BuddingCrystalBlock(BlockBehaviour.Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
|
||||
if (random.nextInt(5) != 0) return;
|
||||
Direction direction = Direction.values()[random.nextInt(Direction.values().length)];
|
||||
BlockPos target = pos.relative(direction);
|
||||
BlockState existing = level.getBlockState(target);
|
||||
Block next = nextStage(existing, direction);
|
||||
if (next == null) return;
|
||||
level.setBlockAndUpdate(target, next.defaultBlockState()
|
||||
.setValue(AmethystClusterBlock.FACING, direction)
|
||||
.setValue(AmethystClusterBlock.WATERLOGGED,
|
||||
existing.getFluidState().getType() == Fluids.WATER));
|
||||
}
|
||||
|
||||
public static Block nextStage(BlockState existing, Direction direction) {
|
||||
if (BuddingAmethystBlock.canClusterGrowAtState(existing)) return Rivencraft.SMALL_CRYSTAL_BUD.get();
|
||||
if (!existing.hasProperty(AmethystClusterBlock.FACING)
|
||||
|| existing.getValue(AmethystClusterBlock.FACING) != direction) return null;
|
||||
if (existing.is(Rivencraft.SMALL_CRYSTAL_BUD)) return Rivencraft.MEDIUM_CRYSTAL_BUD.get();
|
||||
if (existing.is(Rivencraft.MEDIUM_CRYSTAL_BUD)) return Rivencraft.LARGE_CRYSTAL_BUD.get();
|
||||
if (existing.is(Rivencraft.LARGE_CRYSTAL_BUD)) return Rivencraft.CRYSTAL_CLUSTER.get();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+7
-4
@@ -16,11 +16,14 @@ public final class PortBookInteractionHandler {
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onRightClickItem(PlayerInteractEvent.RightClickItem event) {
|
||||
if (!event.getEntity().level().isClientSide()
|
||||
|| !event.getItemStack().is(Rivencraft.LINKING_BOOK.get())) return;
|
||||
if (!event.getEntity().level().isClientSide()) return;
|
||||
boolean linking = event.getItemStack().is(Rivencraft.LINKING_BOOK.get());
|
||||
boolean descriptive = event.getItemStack().is(Rivencraft.DESCRIPTIVE_BOOK.get());
|
||||
if (!linking && !descriptive) return;
|
||||
event.setCanceled(true);
|
||||
event.setCancellationResult(InteractionResult.SUCCESS);
|
||||
Minecraft.getInstance().setScreenAndShow(new PortLinkingBookScreen(
|
||||
event.getHand(), event.getItemStack().copy()));
|
||||
Minecraft.getInstance().setScreenAndShow(linking
|
||||
? new PortLinkingBookScreen(event.getHand(), event.getItemStack().copy())
|
||||
: new PortDescriptiveBookScreen(event.getHand(), event.getItemStack().copy()));
|
||||
}
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
package net.dnorberg.rivencraft.client;
|
||||
|
||||
import com.mojang.blaze3d.platform.InputConstants;
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.dnorberg.rivencraft.network.BindDescriptiveBookPayload;
|
||||
import net.dnorberg.rivencraft.network.PortBookNetworking;
|
||||
import net.dnorberg.rivencraft.network.UseDescriptiveBookPayload;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.components.EditBox;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.client.input.MouseButtonEvent;
|
||||
import net.minecraft.client.renderer.RenderPipelines;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.neoforged.neoforge.client.network.ClientPacketDistributor;
|
||||
|
||||
/** Temporary 26.3 descriptive-book interaction; authored pages and live previews remain unported. */
|
||||
public final class PortDescriptiveBookScreen extends Screen {
|
||||
private static final int WIDTH = 327;
|
||||
private static final int HEIGHT = 199;
|
||||
private static final int TEXT_COLOR = 0xFF3B281B;
|
||||
private static final Identifier COVER = texture("bookui_cover.png");
|
||||
private static final Identifier LEFT_PAGE = texture("notebook_pages.png");
|
||||
private static final Identifier RIGHT_PANEL = texture("bookui_pager.png");
|
||||
|
||||
private final InteractionHand hand;
|
||||
private final boolean bound;
|
||||
private EditBox titleInput;
|
||||
|
||||
public PortDescriptiveBookScreen(InteractionHand hand, ItemStack book) {
|
||||
super(book.getHoverName());
|
||||
this.hand = hand;
|
||||
bound = book.has(Rivencraft.AGE_BOOK_ID.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
if (bound) return;
|
||||
int left = (width - WIDTH) / 2;
|
||||
int top = (height - HEIGHT) / 2;
|
||||
titleInput = new EditBox(font, left + 40, top + 69, 110, 20,
|
||||
Component.literal("Age name"));
|
||||
titleInput.setMaxLength(PortBookNetworking.MAX_BOOK_TITLE_LENGTH);
|
||||
titleInput.setHint(Component.literal("Name this Age"));
|
||||
addRenderableWidget(titleInput);
|
||||
setInitialFocus(titleInput);
|
||||
addRenderableWidget(Button.builder(Component.literal("Bind"), button -> bind())
|
||||
.bounds(left + 40, top + 100, 53, 20).build());
|
||||
addRenderableWidget(Button.builder(Component.literal("Cancel"), button -> onClose())
|
||||
.bounds(left + 97, top + 100, 53, 20).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float partialTick) {
|
||||
int left = (width - WIDTH) / 2;
|
||||
int top = (height - HEIGHT) / 2;
|
||||
blit(graphics, COVER, left, top + 7, 34, 192, 152, 0);
|
||||
blit(graphics, COVER, left + 34, top + 7, 103, 192, 49, 0);
|
||||
blit(graphics, COVER, left + 137, top + 7, 4, 192, 45, 0);
|
||||
blit(graphics, COVER, left + 141, top + 7, 186, 192, 0, 0);
|
||||
blit(graphics, LEFT_PAGE, left + 7, top, 156, 195, 0, 0);
|
||||
blit(graphics, RIGHT_PANEL, left + 163, top, 156, 195, 0, 0);
|
||||
graphics.text(font, title, left + 42, top + 29, TEXT_COLOR, false);
|
||||
if (bound) {
|
||||
graphics.fill(left + 173, top + 20, left + 305, top + 103, 0xFF15141D);
|
||||
graphics.text(font, Component.literal("Age preview unavailable"), left + 181, top + 47,
|
||||
0xFFE9DFCB, false);
|
||||
graphics.text(font, Component.literal("Click to enter"), left + 205, top + 65,
|
||||
0xFFE9DFCB, false);
|
||||
} else {
|
||||
graphics.text(font, Component.literal("Bind a prototype Age"), left + 183, top + 39,
|
||||
TEXT_COLOR, false);
|
||||
}
|
||||
super.extractRenderState(graphics, mouseX, mouseY, partialTick);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) {
|
||||
if (bound && event.button() == InputConstants.MOUSE_BUTTON_LEFT) {
|
||||
int left = (width - WIDTH) / 2;
|
||||
int top = (height - HEIGHT) / 2;
|
||||
if (event.x() >= left + 173 && event.x() < left + 305
|
||||
&& event.y() >= top + 20 && event.y() < top + 103) {
|
||||
ClientPacketDistributor.sendToServer(new UseDescriptiveBookPayload(hand == InteractionHand.OFF_HAND));
|
||||
onClose();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return super.mouseClicked(event, doubleClick);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPauseScreen() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void bind() {
|
||||
if (titleInput == null) return;
|
||||
String name = titleInput.getValue().strip();
|
||||
if (name.isEmpty()) return;
|
||||
ClientPacketDistributor.sendToServer(new BindDescriptiveBookPayload(hand == InteractionHand.OFF_HAND, name));
|
||||
onClose();
|
||||
}
|
||||
|
||||
private static Identifier texture(String filename) {
|
||||
return Identifier.fromNamespaceAndPath(Rivencraft.MOD_ID, "textures/gui/" + filename);
|
||||
}
|
||||
|
||||
private static void blit(GuiGraphicsExtractor graphics, Identifier texture, int x, int y,
|
||||
int width, int height, int u, int v) {
|
||||
graphics.blit(RenderPipelines.GUI_TEXTURED, texture, x, y, u, v, width, height, 256, 256);
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,8 @@ package net.dnorberg.rivencraft.item;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.dnorberg.rivencraft.age.PortAgeTravel;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
@@ -15,7 +13,7 @@ import net.minecraft.world.item.TooltipFlag;
|
||||
import net.minecraft.world.item.component.TooltipDisplay;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
/** First 26.3 bound-book slice; authoring and the book GUI still need porting. */
|
||||
/** Descriptive Book shell for the 26.3 prototype Age backend. */
|
||||
public final class DescriptiveBookItem extends Item {
|
||||
public DescriptiveBookItem(Properties properties) {
|
||||
super(properties);
|
||||
@@ -28,18 +26,8 @@ public final class DescriptiveBookItem extends Item {
|
||||
|
||||
@Override
|
||||
public InteractionResult use(Level level, Player player, InteractionHand hand) {
|
||||
ItemStack stack = player.getItemInHand(hand);
|
||||
Identifier id = stack.get(Rivencraft.AGE_BOOK_ID.get());
|
||||
if (id == null) {
|
||||
if (player instanceof ServerPlayer serverPlayer) {
|
||||
serverPlayer.sendOverlayMessage(Component.literal("Unbound Descriptive Book; use the port's Age book probe"));
|
||||
}
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
if (player instanceof ServerPlayer serverPlayer) {
|
||||
String failure = PortAgeTravel.enter(serverPlayer, id);
|
||||
serverPlayer.sendOverlayMessage(Component.literal(failure == null ? "Entered " + id : failure));
|
||||
}
|
||||
// The client opens the GUI. Binding and travel are validated on the
|
||||
// server through dedicated packets; item use cannot bypass them.
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package net.dnorberg.rivencraft.item;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.util.Prediction;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.item.TooltipFlag;
|
||||
import net.minecraft.world.item.component.TooltipDisplay;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
/** A stackable 16-use vial; using it with paper in the other hand prepares a Link Panel. */
|
||||
public final class InkVialItem extends Item {
|
||||
public static final int CAPACITY = 16;
|
||||
// Match the 1.21.1 default; port the configurable cost with the full configuration system.
|
||||
public static final int LINK_PANEL_COST = 2;
|
||||
|
||||
public InkVialItem(Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
public static int remaining(ItemStack vial) {
|
||||
return vial.getOrDefault(Rivencraft.INK_REMAINING.get(), CAPACITY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResult use(Level level, Player player, InteractionHand hand) {
|
||||
InteractionHand paperHand = hand == InteractionHand.MAIN_HAND
|
||||
? InteractionHand.OFF_HAND : InteractionHand.MAIN_HAND;
|
||||
ItemStack vial = player.getItemInHand(hand);
|
||||
ItemStack paper = player.getItemInHand(paperHand);
|
||||
int ink = remaining(vial);
|
||||
if (!vial.is(Rivencraft.INK_VIAL.get()) || !paper.is(Items.PAPER)
|
||||
|| ink < LINK_PANEL_COST || ink > CAPACITY) {
|
||||
return InteractionResult.PASS;
|
||||
}
|
||||
if (level.isClientSide()) return InteractionResult.SUCCESS;
|
||||
|
||||
ItemStack panel = PageItem.createLinkPanelPage();
|
||||
int newInk = ink - LINK_PANEL_COST;
|
||||
ItemStack spentVial = newInk == 0
|
||||
? new ItemStack(Items.GLASS_BOTTLE)
|
||||
: new ItemStack(Rivencraft.INK_VIAL.get());
|
||||
if (newInk > 0) spentVial.set(Rivencraft.INK_REMAINING.get(), newInk);
|
||||
|
||||
if (paper.getCount() == 1) {
|
||||
player.setItemInHand(paperHand, panel);
|
||||
} else {
|
||||
paper.shrink(1);
|
||||
giveOrDrop(player, panel);
|
||||
}
|
||||
if (vial.getCount() == 1) {
|
||||
player.setItemInHand(hand, spentVial);
|
||||
} else {
|
||||
vial.shrink(1);
|
||||
giveOrDrop(player, spentVial);
|
||||
}
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendHoverText(ItemStack stack, TooltipContext context, TooltipDisplay display,
|
||||
Consumer<Component> tooltip, TooltipFlag flag) {
|
||||
tooltip.accept(Component.translatable("item.rivencraft.ink_vial.remaining", remaining(stack), CAPACITY));
|
||||
tooltip.accept(Component.translatable("item.rivencraft.ink_vial.usage"));
|
||||
}
|
||||
|
||||
private static void giveOrDrop(Player player, ItemStack stack) {
|
||||
if (!player.getInventory().add(stack)) player.drop(stack, false, Prediction.SERVER_ONLY);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package net.dnorberg.rivencraft.item;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.TooltipFlag;
|
||||
import net.minecraft.world.item.component.TooltipDisplay;
|
||||
|
||||
/** One Page item with a persistent Link Panel variant; symbol writing is not ported yet. */
|
||||
public final class PageItem extends Item {
|
||||
public PageItem(Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
public static ItemStack createLinkPanelPage() {
|
||||
ItemStack stack = new ItemStack(Rivencraft.PAGE.get());
|
||||
stack.set(Rivencraft.LINK_PANEL_PAGE.get(), true);
|
||||
return stack;
|
||||
}
|
||||
|
||||
public static boolean isLinkPanel(ItemStack stack) {
|
||||
return stack.is(Rivencraft.PAGE.get()) && stack.getOrDefault(Rivencraft.LINK_PANEL_PAGE.get(), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getName(ItemStack stack) {
|
||||
Component customName = stack.get(DataComponents.CUSTOM_NAME);
|
||||
if (customName != null) return customName;
|
||||
return Component.translatable(isLinkPanel(stack)
|
||||
? "item.rivencraft.page.link_panel" : "item.rivencraft.page.blank");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendHoverText(ItemStack stack, TooltipContext context, TooltipDisplay display,
|
||||
Consumer<Component> tooltip, TooltipFlag flag) {
|
||||
tooltip.accept(Component.translatable(isLinkPanel(stack)
|
||||
? "item.rivencraft.page.link_panel.port_status"
|
||||
: "item.rivencraft.page.blank.port_status"));
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
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.Identifier;
|
||||
|
||||
public record BindDescriptiveBookPayload(boolean offhand, String title) implements CustomPacketPayload {
|
||||
public static final Type<BindDescriptiveBookPayload> TYPE = new Type<>(
|
||||
Identifier.fromNamespaceAndPath(Rivencraft.MOD_ID, "bind_descriptive_book"));
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, BindDescriptiveBookPayload> STREAM_CODEC =
|
||||
StreamCodec.composite(ByteBufCodecs.BOOL, BindDescriptiveBookPayload::offhand,
|
||||
ByteBufCodecs.stringUtf8(32), BindDescriptiveBookPayload::title,
|
||||
BindDescriptiveBookPayload::new);
|
||||
|
||||
@Override
|
||||
public Type<BindDescriptiveBookPayload> type() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,20 @@
|
||||
package net.dnorberg.rivencraft.network;
|
||||
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.dnorberg.rivencraft.age.PortAgeTravel;
|
||||
import net.dnorberg.rivencraft.age.RuntimeAgeBackend;
|
||||
import net.dnorberg.rivencraft.item.LinkBookTarget;
|
||||
import net.dnorberg.rivencraft.item.LinkingBookItem;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.Identifier;
|
||||
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 java.util.Locale;
|
||||
|
||||
/** Server-authoritative handheld Linking Book binding and travel. */
|
||||
public final class PortBookNetworking {
|
||||
@@ -29,6 +33,14 @@ public final class PortBookNetworking {
|
||||
(payload, context) -> {
|
||||
if (context.player() instanceof ServerPlayer player) use(player, payload);
|
||||
});
|
||||
registrar.playToServer(BindDescriptiveBookPayload.TYPE, BindDescriptiveBookPayload.STREAM_CODEC,
|
||||
(payload, context) -> {
|
||||
if (context.player() instanceof ServerPlayer player) bindAge(player, payload);
|
||||
});
|
||||
registrar.playToServer(UseDescriptiveBookPayload.TYPE, UseDescriptiveBookPayload.STREAM_CODEC,
|
||||
(payload, context) -> {
|
||||
if (context.player() instanceof ServerPlayer player) enterAge(player, payload);
|
||||
});
|
||||
}
|
||||
|
||||
private static void bind(ServerPlayer player, BindLinkingBookPayload payload) {
|
||||
@@ -51,6 +63,45 @@ public final class PortBookNetworking {
|
||||
if (failure != null) player.sendOverlayMessage(Component.literal(failure));
|
||||
}
|
||||
|
||||
private static void bindAge(ServerPlayer player, BindDescriptiveBookPayload payload) {
|
||||
ItemStack stack = player.getItemInHand(hand(payload.offhand()));
|
||||
if (!stack.is(Rivencraft.DESCRIPTIVE_BOOK.get()) || stack.has(Rivencraft.AGE_BOOK_ID.get())) return;
|
||||
String title = payload.title().strip();
|
||||
if (title.isEmpty() || title.length() > MAX_BOOK_TITLE_LENGTH) return;
|
||||
String base = title.toLowerCase(Locale.ROOT).replaceAll("[^a-z0-9._-]+", "_")
|
||||
.replaceAll("^[_\\.-]+|[_\\.-]+$", "");
|
||||
if (base.isEmpty()) base = "age";
|
||||
var server = player.level().getServer();
|
||||
String name = base;
|
||||
int suffix = 2;
|
||||
Identifier id = Identifier.fromNamespaceAndPath(Rivencraft.MOD_ID, "age/" + name);
|
||||
while (RuntimeAgeBackend.isNameTaken(server, id)) {
|
||||
name = base + "_" + suffix++;
|
||||
id = Identifier.fromNamespaceAndPath(Rivencraft.MOD_ID, "age/" + name);
|
||||
}
|
||||
try {
|
||||
RuntimeAgeBackend.create(server, id, player.getRandom().nextLong());
|
||||
} catch (IllegalArgumentException | IllegalStateException exception) {
|
||||
player.sendOverlayMessage(Component.literal("Age creation failed: " + exception.getMessage()));
|
||||
return;
|
||||
}
|
||||
stack.set(Rivencraft.AGE_BOOK_ID.get(), id);
|
||||
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.sendOverlayMessage(Component.literal("Bound Descriptive Book: " + title));
|
||||
player.containerMenu.broadcastChanges();
|
||||
}
|
||||
|
||||
private static void enterAge(ServerPlayer player, UseDescriptiveBookPayload payload) {
|
||||
ItemStack stack = player.getItemInHand(hand(payload.offhand()));
|
||||
if (!stack.is(Rivencraft.DESCRIPTIVE_BOOK.get())) return;
|
||||
Identifier id = stack.get(Rivencraft.AGE_BOOK_ID.get());
|
||||
if (id == null) return;
|
||||
String failure = PortAgeTravel.enter(player, id);
|
||||
if (failure != null) player.sendOverlayMessage(Component.literal(failure));
|
||||
}
|
||||
|
||||
private static InteractionHand hand(boolean offhand) {
|
||||
return offhand ? InteractionHand.OFF_HAND : InteractionHand.MAIN_HAND;
|
||||
}
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
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.Identifier;
|
||||
|
||||
public record UseDescriptiveBookPayload(boolean offhand) implements CustomPacketPayload {
|
||||
public static final Type<UseDescriptiveBookPayload> TYPE = new Type<>(
|
||||
Identifier.fromNamespaceAndPath(Rivencraft.MOD_ID, "use_descriptive_book"));
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, UseDescriptiveBookPayload> STREAM_CODEC =
|
||||
StreamCodec.composite(ByteBufCodecs.BOOL, UseDescriptiveBookPayload::offhand,
|
||||
UseDescriptiveBookPayload::new);
|
||||
|
||||
@Override
|
||||
public Type<UseDescriptiveBookPayload> type() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package net.dnorberg.rivencraft.registry;
|
||||
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.dnorberg.rivencraft.item.PageItem;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.item.CreativeModeTab;
|
||||
import net.neoforged.bus.api.IEventBus;
|
||||
import net.neoforged.neoforge.registries.DeferredRegister;
|
||||
|
||||
/** Only items already ported to 26.3 appear here. */
|
||||
public final class ModCreativeTabs {
|
||||
private static final DeferredRegister<CreativeModeTab> TABS =
|
||||
DeferredRegister.create(Registries.CREATIVE_MODE_TAB, Rivencraft.MOD_ID);
|
||||
|
||||
static {
|
||||
TABS.register("rivencraft", () -> CreativeModeTab.builder()
|
||||
.title(Component.translatable("itemGroup.rivencraft"))
|
||||
.icon(() -> Rivencraft.DESCRIPTIVE_BOOK.get().getDefaultInstance())
|
||||
.displayItems((parameters, output) -> {
|
||||
output.accept(Rivencraft.DESCRIPTIVE_BOOK.get());
|
||||
output.accept(Rivencraft.LINKING_BOOK.get());
|
||||
output.accept(Rivencraft.CRYSTAL_BLOCK_ITEM.get());
|
||||
output.accept(Rivencraft.BUDDING_CRYSTAL_ITEM.get());
|
||||
output.accept(Rivencraft.SMALL_CRYSTAL_BUD_ITEM.get());
|
||||
output.accept(Rivencraft.MEDIUM_CRYSTAL_BUD_ITEM.get());
|
||||
output.accept(Rivencraft.LARGE_CRYSTAL_BUD_ITEM.get());
|
||||
output.accept(Rivencraft.CRYSTAL_CLUSTER_ITEM.get());
|
||||
output.accept(Rivencraft.CRYSTAL_SHARD.get());
|
||||
output.accept(Rivencraft.INK_VIAL.get());
|
||||
})
|
||||
.build());
|
||||
TABS.register("rivencraft_link_pages", () -> CreativeModeTab.builder()
|
||||
.title(Component.translatable("itemGroup.rivencraft.pages.link"))
|
||||
.icon(PageItem::createLinkPanelPage)
|
||||
.displayItems((parameters, output) -> output.accept(PageItem.createLinkPanelPage()))
|
||||
.build());
|
||||
}
|
||||
|
||||
private ModCreativeTabs() {
|
||||
}
|
||||
|
||||
public static void register(IEventBus modEventBus) {
|
||||
TABS.register(modEventBus);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package net.dnorberg.rivencraft.writing;
|
||||
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.dnorberg.rivencraft.item.PageItem;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.item.crafting.CraftingInput;
|
||||
import net.minecraft.world.item.crafting.CustomRecipe;
|
||||
import net.minecraft.world.item.crafting.RecipeSerializer;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
/** Legacy's shapeless leather + Link Panel recipe; never accepts a blank Page. */
|
||||
public final class LinkingBookRecipe extends CustomRecipe {
|
||||
private static final LinkingBookRecipe INSTANCE = new LinkingBookRecipe();
|
||||
public static final MapCodec<LinkingBookRecipe> MAP_CODEC = MapCodec.unit(INSTANCE);
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, LinkingBookRecipe> STREAM_CODEC = StreamCodec.unit(INSTANCE);
|
||||
public static final RecipeSerializer<LinkingBookRecipe> SERIALIZER = new RecipeSerializer<>(MAP_CODEC, STREAM_CODEC);
|
||||
|
||||
private LinkingBookRecipe() {
|
||||
}
|
||||
|
||||
private static ItemStack findPanel(CraftingInput input) {
|
||||
if (input.ingredientCount() != 2) return ItemStack.EMPTY;
|
||||
ItemStack panel = ItemStack.EMPTY;
|
||||
boolean leather = false;
|
||||
for (ItemStack stack : input.items()) {
|
||||
if (stack.isEmpty()) continue;
|
||||
if (PageItem.isLinkPanel(stack) && panel.isEmpty()) {
|
||||
panel = stack;
|
||||
} else if (stack.is(Items.LEATHER) && !leather) {
|
||||
leather = true;
|
||||
} else {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
}
|
||||
return leather ? panel : ItemStack.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(CraftingInput input, Level level) {
|
||||
return !findPanel(input).isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack assemble(CraftingInput input) {
|
||||
ItemStack panel = findPanel(input);
|
||||
if (panel.isEmpty()) return ItemStack.EMPTY;
|
||||
ItemStack book = new ItemStack(Rivencraft.LINKING_BOOK.get());
|
||||
if (panel.has(DataComponents.CUSTOM_NAME)) {
|
||||
book.set(DataComponents.CUSTOM_NAME, panel.get(DataComponents.CUSTOM_NAME));
|
||||
}
|
||||
return book;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecipeSerializer<LinkingBookRecipe> getSerializer() {
|
||||
return SERIALIZER;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"model":{"type":"minecraft:model","model":"rivencraft:item/budding_crystal"}}
|
||||
@@ -0,0 +1 @@
|
||||
{"model":{"type":"minecraft:model","model":"rivencraft:item/crystal_cluster"}}
|
||||
@@ -0,0 +1 @@
|
||||
{"model":{"type":"minecraft:model","model":"rivencraft:item/ink_vial"}}
|
||||
@@ -0,0 +1 @@
|
||||
{"model":{"type":"minecraft:model","model":"rivencraft:item/large_crystal_bud"}}
|
||||
@@ -1,6 +1,15 @@
|
||||
{
|
||||
"model": {
|
||||
"type": "minecraft:model",
|
||||
"model": "rivencraft:item/linking_book_bound"
|
||||
"type": "minecraft:condition",
|
||||
"property": "minecraft:has_component",
|
||||
"component": "rivencraft:link_book_target",
|
||||
"on_true": {
|
||||
"type": "minecraft:model",
|
||||
"model": "rivencraft:item/linking_book_bound"
|
||||
},
|
||||
"on_false": {
|
||||
"type": "minecraft:model",
|
||||
"model": "rivencraft:item/linking_book_unwritten"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"model":{"type":"minecraft:model","model":"rivencraft:item/medium_crystal_bud"}}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"model": {
|
||||
"type": "minecraft:condition",
|
||||
"property": "minecraft:has_component",
|
||||
"component": "rivencraft:link_panel_page",
|
||||
"on_true": {"type": "minecraft:model", "model": "rivencraft:item/page_link_panel"},
|
||||
"on_false": {"type": "minecraft:model", "model": "rivencraft:item/page"}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"model":{"type":"minecraft:model","model":"rivencraft:item/small_crystal_bud"}}
|
||||
@@ -2,5 +2,20 @@
|
||||
"item.rivencraft.crystal_shard": "Crystal Shard",
|
||||
"item.rivencraft.descriptive_book": "Descriptive Book",
|
||||
"item.rivencraft.linking_book": "Linking Book",
|
||||
"block.rivencraft.crystal_block": "Crystal Block"
|
||||
"item.rivencraft.page": "Page",
|
||||
"item.rivencraft.page.blank": "Blank Page",
|
||||
"item.rivencraft.page.link_panel": "Link Panel Page",
|
||||
"item.rivencraft.ink_vial": "Ink Vial",
|
||||
"item.rivencraft.ink_vial.remaining": "Ink: %s/%s",
|
||||
"item.rivencraft.ink_vial.usage": "Use with paper in the other hand to prepare a Link Panel.",
|
||||
"item.rivencraft.page.blank.port_status": "Base for future symbol writing; not yet usable in this port.",
|
||||
"item.rivencraft.page.link_panel.port_status": "Prepared for book binding; the Book Binder is not yet ported.",
|
||||
"block.rivencraft.crystal_block": "Crystal Block",
|
||||
"block.rivencraft.budding_crystal": "Budding Crystal",
|
||||
"block.rivencraft.small_crystal_bud": "Small Crystal Bud",
|
||||
"block.rivencraft.medium_crystal_bud": "Medium Crystal Bud",
|
||||
"block.rivencraft.large_crystal_bud": "Large Crystal Bud",
|
||||
"block.rivencraft.crystal_cluster": "Crystal Cluster",
|
||||
"itemGroup.rivencraft": "Rivencraft",
|
||||
"itemGroup.rivencraft.pages.link": "Rivencraft Link Pages"
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"parent": "minecraft:item/generated",
|
||||
"textures": {
|
||||
"layer0": "rivencraft:item/linking_book_unwritten"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"parent":"minecraft:item/generated","textures":{"layer0":"rivencraft:item/page"}}
|
||||
@@ -0,0 +1 @@
|
||||
{"type":"minecraft:block","random_sequence":"rivencraft:blocks/budding_crystal"}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"type": "minecraft:block",
|
||||
"pools": [{
|
||||
"entries": [{
|
||||
"type": "minecraft:alternatives",
|
||||
"children": [
|
||||
{"type": "minecraft:item", "condition": "minecraft:tool/can_silk_touch", "name": "rivencraft:crystal_cluster"},
|
||||
{"type": "minecraft:alternatives", "children": [
|
||||
{"type": "minecraft:item", "condition": {"type": "minecraft:match_tool", "predicate": {"items": "#minecraft:cluster_max_harvestables"}}, "modifier": [
|
||||
{"type": "minecraft:set_count", "count": 4},
|
||||
{"type": "minecraft:apply_bonus", "enchantment": "minecraft:fortune", "formula": "minecraft:ore_drops"}
|
||||
], "name": "rivencraft:crystal_shard"},
|
||||
{"type": "minecraft:item", "modifier": [
|
||||
{"type": "minecraft:set_count", "count": 2},
|
||||
{"type": "minecraft:explosion_decay"}
|
||||
], "name": "rivencraft:crystal_shard"}
|
||||
]}
|
||||
]
|
||||
}],
|
||||
"rolls": 1
|
||||
}],
|
||||
"random_sequence": "rivencraft:blocks/crystal_cluster"
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"type":"minecraft:block","pools":[{"condition":"minecraft:tool/can_silk_touch","entries":[{"type":"minecraft:item","name":"rivencraft:large_crystal_bud"}],"rolls":1}],"random_sequence":"rivencraft:blocks/large_crystal_bud"}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"type":"minecraft:block","pools":[{"condition":"minecraft:tool/can_silk_touch","entries":[{"type":"minecraft:item","name":"rivencraft:medium_crystal_bud"}],"rolls":1}],"random_sequence":"rivencraft:blocks/medium_crystal_bud"}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"type":"minecraft:block","pools":[{"condition":"minecraft:tool/can_silk_touch","entries":[{"type":"minecraft:item","name":"rivencraft:small_crystal_bud"}],"rolls":1}],"random_sequence":"rivencraft:blocks/small_crystal_bud"}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"type": "minecraft:crafting_shaped",
|
||||
"category": "misc",
|
||||
"pattern": ["SS", "SS"],
|
||||
"key": {
|
||||
"S": "rivencraft:crystal_shard"
|
||||
},
|
||||
"result": {
|
||||
"id": "rivencraft:crystal_block",
|
||||
"count": 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"type": "minecraft:crafting_shapeless",
|
||||
"category": "misc",
|
||||
"ingredients": [
|
||||
"minecraft:black_dye",
|
||||
"minecraft:black_dye",
|
||||
"minecraft:glass_bottle",
|
||||
"minecraft:water_bucket"
|
||||
],
|
||||
"result": {"id": "rivencraft:ink_vial", "count": 1}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"type":"rivencraft:linking_book"}
|
||||
Reference in new issue
Block a user