diff --git a/docs/MINECRAFT_26_3_PORT.md b/docs/MINECRAFT_26_3_PORT.md index 938361e..d1fec7f 100644 --- a/docs/MINECRAFT_26_3_PORT.md +++ b/docs/MINECRAFT_26_3_PORT.md @@ -381,6 +381,44 @@ 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 diff --git a/ports/mc263/README.md b/ports/mc263/README.md index 05ca3bc..45c6078 100644 --- a/ports/mc263/README.md +++ b/ports/mc263/README.md @@ -11,6 +11,25 @@ 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 diff --git a/ports/mc263/build.gradle b/ports/mc263/build.gradle index 74bf48c..3b1f791 100644 --- a/ports/mc263/build.gradle +++ b/ports/mc263/build.gradle @@ -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' } } diff --git a/ports/mc263/gradle.properties b/ports/mc263/gradle.properties index e25dbb6..911f33d 100644 --- a/ports/mc263/gradle.properties +++ b/ports/mc263/gradle.properties @@ -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.3-alpha.1 +mod_version=1.0.1.9-alpha.1 diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/Rivencraft.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/Rivencraft.java index f53d438..ebdc32e 100644 --- a/ports/mc263/src/main/java/net/dnorberg/rivencraft/Rivencraft.java +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/Rivencraft.java @@ -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> 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> 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> LINK_PANEL_PAGE = COMPONENTS.registerComponentType( + "link_panel_page", builder -> builder.persistent(Codec.BOOL) + .networkSynchronized(ByteBufCodecs.BOOL)); + public static final Supplier> INK_REMAINING = COMPONENTS.registerComponentType( + "ink_remaining", builder -> builder.persistent(Codec.INT) + .networkSynchronized(ByteBufCodecs.VAR_INT)); public static final DeferredBlock CRYSTAL_BLOCK = BLOCKS.registerBlock("crystal_block", Block::new, () -> BlockBehaviour.Properties.ofFullCopy(Blocks.AMETHYST_BLOCK)); public static final DeferredItem CRYSTAL_BLOCK_ITEM = ITEMS.registerSimpleBlockItem(CRYSTAL_BLOCK); + public static final DeferredBlock BUDDING_CRYSTAL = BLOCKS.registerBlock( + "budding_crystal", BuddingCrystalBlock::new, + () -> BlockBehaviour.Properties.ofFullCopy(Blocks.BUDDING_AMETHYST).mapColor(MapColor.COLOR_BLUE)); + public static final DeferredItem BUDDING_CRYSTAL_ITEM = ITEMS.registerSimpleBlockItem(BUDDING_CRYSTAL); + public static final DeferredBlock 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 SMALL_CRYSTAL_BUD_ITEM = ITEMS.registerSimpleBlockItem(SMALL_CRYSTAL_BUD); + public static final DeferredBlock 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 MEDIUM_CRYSTAL_BUD_ITEM = ITEMS.registerSimpleBlockItem(MEDIUM_CRYSTAL_BUD); + public static final DeferredBlock 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 LARGE_CRYSTAL_BUD_ITEM = ITEMS.registerSimpleBlockItem(LARGE_CRYSTAL_BUD); + public static final DeferredBlock CRYSTAL_CLUSTER = BLOCKS.registerBlock( + "crystal_cluster", properties -> new AmethystClusterBlock(7.0F, 3.0F, properties), + () -> BlockBehaviour.Properties.ofFullCopy(Blocks.AMETHYST_CLUSTER)); + public static final DeferredItem CRYSTAL_CLUSTER_ITEM = ITEMS.registerSimpleBlockItem(CRYSTAL_CLUSTER); public static final DeferredItem CRYSTAL_SHARD = ITEMS.registerSimpleItem("crystal_shard"); + public static final DeferredItem PAGE = ITEMS.registerItem("page", PageItem::new); + public static final DeferredItem INK_VIAL = ITEMS.registerItem( + "ink_vial", InkVialItem::new, properties -> properties.stacksTo(16)); public static final DeferredItem DESCRIPTIVE_BOOK = ITEMS.registerItem( "descriptive_book", DescriptiveBookItem::new, properties -> properties.stacksTo(1)); public static final DeferredItem LINKING_BOOK = ITEMS.registerItem( "linking_book", LinkingBookItem::new, properties -> properties.stacksTo(1)); + public static final Supplier> 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); diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/block/BuddingCrystalBlock.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/block/BuddingCrystalBlock.java new file mode 100644 index 0000000..75325f0 --- /dev/null +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/block/BuddingCrystalBlock.java @@ -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; + } +} diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/item/InkVialItem.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/item/InkVialItem.java new file mode 100644 index 0000000..a5a9e78 --- /dev/null +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/item/InkVialItem.java @@ -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 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); + } +} diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/item/PageItem.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/item/PageItem.java new file mode 100644 index 0000000..ac7dc75 --- /dev/null +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/item/PageItem.java @@ -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 tooltip, TooltipFlag flag) { + tooltip.accept(Component.translatable(isLinkPanel(stack) + ? "item.rivencraft.page.link_panel.port_status" + : "item.rivencraft.page.blank.port_status")); + } +} diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/registry/ModCreativeTabs.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/registry/ModCreativeTabs.java new file mode 100644 index 0000000..831d823 --- /dev/null +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/registry/ModCreativeTabs.java @@ -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 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); + } +} diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/LinkingBookRecipe.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/LinkingBookRecipe.java new file mode 100644 index 0000000..ebba3d2 --- /dev/null +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/LinkingBookRecipe.java @@ -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 MAP_CODEC = MapCodec.unit(INSTANCE); + public static final StreamCodec STREAM_CODEC = StreamCodec.unit(INSTANCE); + public static final RecipeSerializer 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 getSerializer() { + return SERIALIZER; + } +} diff --git a/ports/mc263/src/main/resources/assets/rivencraft/items/budding_crystal.json b/ports/mc263/src/main/resources/assets/rivencraft/items/budding_crystal.json new file mode 100644 index 0000000..55b8c32 --- /dev/null +++ b/ports/mc263/src/main/resources/assets/rivencraft/items/budding_crystal.json @@ -0,0 +1 @@ +{"model":{"type":"minecraft:model","model":"rivencraft:item/budding_crystal"}} diff --git a/ports/mc263/src/main/resources/assets/rivencraft/items/crystal_cluster.json b/ports/mc263/src/main/resources/assets/rivencraft/items/crystal_cluster.json new file mode 100644 index 0000000..50f5df8 --- /dev/null +++ b/ports/mc263/src/main/resources/assets/rivencraft/items/crystal_cluster.json @@ -0,0 +1 @@ +{"model":{"type":"minecraft:model","model":"rivencraft:item/crystal_cluster"}} diff --git a/ports/mc263/src/main/resources/assets/rivencraft/items/ink_vial.json b/ports/mc263/src/main/resources/assets/rivencraft/items/ink_vial.json new file mode 100644 index 0000000..2f93a93 --- /dev/null +++ b/ports/mc263/src/main/resources/assets/rivencraft/items/ink_vial.json @@ -0,0 +1 @@ +{"model":{"type":"minecraft:model","model":"rivencraft:item/ink_vial"}} diff --git a/ports/mc263/src/main/resources/assets/rivencraft/items/large_crystal_bud.json b/ports/mc263/src/main/resources/assets/rivencraft/items/large_crystal_bud.json new file mode 100644 index 0000000..21c9b77 --- /dev/null +++ b/ports/mc263/src/main/resources/assets/rivencraft/items/large_crystal_bud.json @@ -0,0 +1 @@ +{"model":{"type":"minecraft:model","model":"rivencraft:item/large_crystal_bud"}} diff --git a/ports/mc263/src/main/resources/assets/rivencraft/items/linking_book.json b/ports/mc263/src/main/resources/assets/rivencraft/items/linking_book.json index ac1d96b..a1f32f2 100644 --- a/ports/mc263/src/main/resources/assets/rivencraft/items/linking_book.json +++ b/ports/mc263/src/main/resources/assets/rivencraft/items/linking_book.json @@ -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" + } } } diff --git a/ports/mc263/src/main/resources/assets/rivencraft/items/medium_crystal_bud.json b/ports/mc263/src/main/resources/assets/rivencraft/items/medium_crystal_bud.json new file mode 100644 index 0000000..bec817e --- /dev/null +++ b/ports/mc263/src/main/resources/assets/rivencraft/items/medium_crystal_bud.json @@ -0,0 +1 @@ +{"model":{"type":"minecraft:model","model":"rivencraft:item/medium_crystal_bud"}} diff --git a/ports/mc263/src/main/resources/assets/rivencraft/items/page.json b/ports/mc263/src/main/resources/assets/rivencraft/items/page.json new file mode 100644 index 0000000..67de65e --- /dev/null +++ b/ports/mc263/src/main/resources/assets/rivencraft/items/page.json @@ -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"} + } +} diff --git a/ports/mc263/src/main/resources/assets/rivencraft/items/small_crystal_bud.json b/ports/mc263/src/main/resources/assets/rivencraft/items/small_crystal_bud.json new file mode 100644 index 0000000..64ac2dd --- /dev/null +++ b/ports/mc263/src/main/resources/assets/rivencraft/items/small_crystal_bud.json @@ -0,0 +1 @@ +{"model":{"type":"minecraft:model","model":"rivencraft:item/small_crystal_bud"}} diff --git a/ports/mc263/src/main/resources/assets/rivencraft/lang/en_us.json b/ports/mc263/src/main/resources/assets/rivencraft/lang/en_us.json index 0edf004..f2c9eee 100644 --- a/ports/mc263/src/main/resources/assets/rivencraft/lang/en_us.json +++ b/ports/mc263/src/main/resources/assets/rivencraft/lang/en_us.json @@ -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" } diff --git a/ports/mc263/src/main/resources/assets/rivencraft/models/item/linking_book_unwritten.json b/ports/mc263/src/main/resources/assets/rivencraft/models/item/linking_book_unwritten.json new file mode 100644 index 0000000..43f66dc --- /dev/null +++ b/ports/mc263/src/main/resources/assets/rivencraft/models/item/linking_book_unwritten.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "rivencraft:item/linking_book_unwritten" + } +} diff --git a/ports/mc263/src/main/resources/assets/rivencraft/models/item/page.json b/ports/mc263/src/main/resources/assets/rivencraft/models/item/page.json new file mode 100644 index 0000000..15a91a3 --- /dev/null +++ b/ports/mc263/src/main/resources/assets/rivencraft/models/item/page.json @@ -0,0 +1 @@ +{"parent":"minecraft:item/generated","textures":{"layer0":"rivencraft:item/page"}} diff --git a/ports/mc263/src/main/resources/data/rivencraft/loot_table/blocks/budding_crystal.json b/ports/mc263/src/main/resources/data/rivencraft/loot_table/blocks/budding_crystal.json new file mode 100644 index 0000000..900639a --- /dev/null +++ b/ports/mc263/src/main/resources/data/rivencraft/loot_table/blocks/budding_crystal.json @@ -0,0 +1 @@ +{"type":"minecraft:block","random_sequence":"rivencraft:blocks/budding_crystal"} diff --git a/ports/mc263/src/main/resources/data/rivencraft/loot_table/blocks/crystal_cluster.json b/ports/mc263/src/main/resources/data/rivencraft/loot_table/blocks/crystal_cluster.json new file mode 100644 index 0000000..78562d2 --- /dev/null +++ b/ports/mc263/src/main/resources/data/rivencraft/loot_table/blocks/crystal_cluster.json @@ -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" +} diff --git a/ports/mc263/src/main/resources/data/rivencraft/loot_table/blocks/large_crystal_bud.json b/ports/mc263/src/main/resources/data/rivencraft/loot_table/blocks/large_crystal_bud.json new file mode 100644 index 0000000..9e8feac --- /dev/null +++ b/ports/mc263/src/main/resources/data/rivencraft/loot_table/blocks/large_crystal_bud.json @@ -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"} diff --git a/ports/mc263/src/main/resources/data/rivencraft/loot_table/blocks/medium_crystal_bud.json b/ports/mc263/src/main/resources/data/rivencraft/loot_table/blocks/medium_crystal_bud.json new file mode 100644 index 0000000..9a72cdd --- /dev/null +++ b/ports/mc263/src/main/resources/data/rivencraft/loot_table/blocks/medium_crystal_bud.json @@ -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"} diff --git a/ports/mc263/src/main/resources/data/rivencraft/loot_table/blocks/small_crystal_bud.json b/ports/mc263/src/main/resources/data/rivencraft/loot_table/blocks/small_crystal_bud.json new file mode 100644 index 0000000..e50a4c1 --- /dev/null +++ b/ports/mc263/src/main/resources/data/rivencraft/loot_table/blocks/small_crystal_bud.json @@ -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"} diff --git a/ports/mc263/src/main/resources/data/rivencraft/recipe/crystal_block.json b/ports/mc263/src/main/resources/data/rivencraft/recipe/crystal_block.json new file mode 100644 index 0000000..0ccfda7 --- /dev/null +++ b/ports/mc263/src/main/resources/data/rivencraft/recipe/crystal_block.json @@ -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 + } +} diff --git a/ports/mc263/src/main/resources/data/rivencraft/recipe/ink_vial.json b/ports/mc263/src/main/resources/data/rivencraft/recipe/ink_vial.json new file mode 100644 index 0000000..7d74986 --- /dev/null +++ b/ports/mc263/src/main/resources/data/rivencraft/recipe/ink_vial.json @@ -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} +} diff --git a/ports/mc263/src/main/resources/data/rivencraft/recipe/linking_book.json b/ports/mc263/src/main/resources/data/rivencraft/recipe/linking_book.json new file mode 100644 index 0000000..afc691f --- /dev/null +++ b/ports/mc263/src/main/resources/data/rivencraft/recipe/linking_book.json @@ -0,0 +1 @@ +{"type":"rivencraft:linking_book"}