From 68db7952164cab13f2667b7e6ccc133a2319ee84 Mon Sep 17 00:00:00 2001 From: Dustin Norberg Date: Sat, 3 Oct 2026 07:46:49 -0400 Subject: [PATCH] Cover panel-only book modifiers with regression test --- README.md | 6 ++- gradle.properties | 2 +- .../gametest/BookBinderGameTests.java | 48 +++++++++++++++++++ .../rivencraft/gametest/CoreGameTests.java | 5 ++ 4 files changed, 58 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 950d52f..5b45235 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,10 @@ and link-panel costs in the `writing` section of `rivencraft-server.toml`. The Ink Mixer now offers a Legacy-style station for producing Link Panels: insert paper and Ink Vials, then take a Link Panel from the result slot. Each vial fills its 16-unit basin and returns a glass bottle; no server tick is -needed. The earlier paper-in-one-hand method remains available. Ink modifier -ingredients and link properties are planned for Milestone 5. +needed. The earlier paper-in-one-hand method remains available. Ink Mixer +additives can roll Disarm, Maintain Momentum, and Intra Linking onto a Link +Panel. Descriptive Books retain Disarm and Momentum; Intra Linking is only +meaningful for same-dimension travel with a Linking Book. The Writing Desk can copy a selected written page from its collection tabs. The Writing Desk is now craftable from planks, a glass bottle, and a feather. It occupies two adjacent blocks, with one shared inventory; loaded paper and diff --git a/gradle.properties b/gradle.properties index 77060ad..2fbef9e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -36,7 +36,7 @@ mod_name=Rivencraft # 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 # The mod version. Rivencraft uses the x.y.z.b policy documented in README.md. -mod_version=0.4.7.3 +mod_version=0.4.7.4 immersive_portals_version=6.0.7 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. diff --git a/src/main/java/net/dnorberg/rivencraft/gametest/BookBinderGameTests.java b/src/main/java/net/dnorberg/rivencraft/gametest/BookBinderGameTests.java index 64f9787..cd5c5cc 100644 --- a/src/main/java/net/dnorberg/rivencraft/gametest/BookBinderGameTests.java +++ b/src/main/java/net/dnorberg/rivencraft/gametest/BookBinderGameTests.java @@ -6,6 +6,9 @@ import net.dnorberg.rivencraft.block.entity.BookBinderBlockEntity; import net.dnorberg.rivencraft.book.AgeReference; import net.dnorberg.rivencraft.book.AgeDimensionService; import net.dnorberg.rivencraft.book.AgeRegistrySavedData; +import net.dnorberg.rivencraft.book.LinkProperties; +import net.dnorberg.rivencraft.book.LinkTarget; +import net.dnorberg.rivencraft.book.LinkingService; import net.dnorberg.rivencraft.item.PageItem; import net.dnorberg.rivencraft.menu.BookBinderMenu; import net.dnorberg.rivencraft.registry.ModBlocks; @@ -17,6 +20,7 @@ import net.dnorberg.rivencraft.writing.SymbolReference; import net.dnorberg.rivencraft.writing.SymbolCatalog; import net.dnorberg.rivencraft.writing.WritingEntry; import net.minecraft.core.BlockPos; +import net.minecraft.core.GlobalPos; import net.minecraft.gametest.framework.GameTest; import net.minecraft.gametest.framework.GameTestHelper; import net.minecraft.resources.ResourceLocation; @@ -185,6 +189,50 @@ public final class BookBinderGameTests { helper.succeed(); } + public static void panelOnlyBookRetainsApplicableModifiers(GameTestHelper helper) { + BookBinderBlockEntity binder = place(helper); + binder.setTitle("Modified Random Age"); + binder.setItem(BookBinderBlockEntity.COVER, new ItemStack(Items.LEATHER)); + ItemStack panel = PageItem.createLinkPanelPage(); + panel.set(ModDataComponents.LINK_PROPERTIES.get(), new LinkProperties( + LinkProperties.DISARM | LinkProperties.MAINTAIN_MOMENTUM | LinkProperties.INTRA_LINKING)); + var player = helper.makeMockServerPlayerInLevel(); + player.moveTo(binder.getBlockPos().getX() + 0.5, binder.getBlockPos().getY() + 0.5, + binder.getBlockPos().getZ() + 0.5); + BookBinderMenu menu = new BookBinderMenu(1, player.getInventory(), binder); + menu.setCarried(panel); + helper.assertTrue(menu.clickMenuButton(player, BookBinderMenu.STRIP_CLICK_BASE), + "The link strip must accept the modified panel without symbol pages"); + ItemStack result = menu.quickMoveStack(player, BookBinderMenu.RESULT); + helper.assertTrue(result.is(ModItems.DESCRIPTIVE_BOOK.get()), + "A Link Panel alone must create a random-Age Descriptive Book"); + LinkProperties expected = new LinkProperties(LinkProperties.DISARM | LinkProperties.MAINTAIN_MOMENTUM); + helper.assertValueEqual(result.get(ModDataComponents.LINK_PROPERTIES.get()), expected, + "The panel-only book must keep travel modifiers but omit Intra Linking"); + ItemStack restored = ItemStack.parseOptional(helper.getLevel().registryAccess(), + (net.minecraft.nbt.CompoundTag) result.saveOptional(helper.getLevel().registryAccess())); + helper.assertValueEqual(restored.get(ModDataComponents.LINK_PROPERTIES.get()), expected, + "Panel-only book modifiers must survive save and reload"); + BlockPos departure = player.blockPosition(); + BlockPos destination = departure.offset(3, 0, 0); + helper.getLevel().setBlock(destination.below(), Blocks.STONE.defaultBlockState(), 3); + helper.getLevel().setBlock(destination, Blocks.AIR.defaultBlockState(), 3); + helper.getLevel().setBlock(destination.above(), Blocks.AIR.defaultBlockState(), 3); + player.getInventory().setItem(4, new ItemStack(Items.DIAMOND, 3)); + LinkTarget target = new LinkTarget(GlobalPos.of(helper.getLevel().dimension(), destination), 0.0F, 0.0F); + helper.assertValueEqual(LinkingService.travelWithFooting(player, target, + restored.get(ModDataComponents.LINK_PROPERTIES.get()).forDescriptiveBook()), + LinkingService.LinkResult.SUCCESS, + "The panel-only book's properties must be accepted by grounded travel"); + helper.assertTrue(player.getInventory().getItem(4).isEmpty(), + "Disarm must remove the carried items during panel-only book travel"); + helper.assertTrue(helper.getLevel().getEntitiesOfClass(ItemEntity.class, + new AABB(departure).inflate(5.0), entity -> entity.getItem().is(Items.DIAMOND)) + .stream().mapToInt(entity -> entity.getItem().getCount()).sum() >= 3, + "Disarm must drop the items at the departure location"); + helper.succeed(); + } + @GameTest(template = "empty") public static void linkPanelOnlyCreatesCompletedRandomAgeBook(GameTestHelper helper) { BookBinderBlockEntity binder = place(helper); diff --git a/src/main/java/net/dnorberg/rivencraft/gametest/CoreGameTests.java b/src/main/java/net/dnorberg/rivencraft/gametest/CoreGameTests.java index a761001..33dc063 100644 --- a/src/main/java/net/dnorberg/rivencraft/gametest/CoreGameTests.java +++ b/src/main/java/net/dnorberg/rivencraft/gametest/CoreGameTests.java @@ -87,6 +87,11 @@ public final class CoreGameTests { BookBinderGameTests.assemblyIsAtomicAndRetainsPageOrder(helper); } + @GameTest(template = "empty") + public static void panelOnlyBookModifiers(GameTestHelper helper) { + BookBinderGameTests.panelOnlyBookRetainsApplicableModifiers(helper); + } + @GameTest(template = "empty") public static void binderRejectsConflictingGrammar(GameTestHelper helper) { BookBinderGameTests.conflictingGrammarKeepsBinderInputsRecoverable(helper);