Public Access
Complete Milestone 4 writing acceptance and migration coverage (#26, #27)
Build / build (push) Canceled after 0s
Build / build (push) Canceled after 0s
This commit is contained in:
1 parent
ba2faace15
commit
8246640f49
9 files changed
+165
-12
No files matched your search
@@ -105,7 +105,7 @@ public final class BookBinderBlockEntity extends BlockEntity implements Containe
|
||||
|| items.get(LINK_PANEL).getCount() != 1) return false;
|
||||
for (int slot = FIRST_PAGE; slot < SIZE; slot++) {
|
||||
ItemStack page = items.get(slot);
|
||||
if (!page.isEmpty() && (!validPage(page) || page.getCount() != 1
|
||||
if (!page.isEmpty() && (!validPage(page) || !PageItem.isUsableWriting(page) || page.getCount() != 1
|
||||
|| PageItem.content(page).kind() == PageContent.Kind.BLANK)) return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -74,6 +74,25 @@ public final class BookBinderGameTests {
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void missingSymbolPageSurvivesReloadButCannotBeAssembled(GameTestHelper helper) {
|
||||
BookBinderBlockEntity binder = place(helper);
|
||||
binder.setTitle("Recoverable Writing");
|
||||
binder.setItem(BookBinderBlockEntity.COVER, new ItemStack(Items.LEATHER));
|
||||
binder.setItem(BookBinderBlockEntity.LINK_PANEL, PageItem.createLinkPanelPage());
|
||||
SymbolReference missing = new SymbolReference(ResourceLocation.parse("example:removed_symbol"));
|
||||
binder.setItem(BookBinderBlockEntity.FIRST_PAGE, PageItem.createRecoveredPage(missing));
|
||||
var saved = binder.saveWithoutMetadata(helper.getLevel().registryAccess());
|
||||
var restored = new BookBinderBlockEntity(binder.getBlockPos(), binder.getBlockState());
|
||||
restored.loadWithComponents(saved, helper.getLevel().registryAccess());
|
||||
helper.assertValueEqual(PageItem.content(restored.getItem(BookBinderBlockEntity.FIRST_PAGE))
|
||||
.symbol().orElseThrow(), missing, "Reload must preserve the removed data-pack symbol ID");
|
||||
helper.assertFalse(restored.canAssemble(), "Missing symbol cannot create a new written Age");
|
||||
helper.assertTrue(restored.getItem(BookBinderBlockEntity.FIRST_PAGE).is(ModItems.PAGE.get()),
|
||||
"Rejected writing must remain recoverable as a Page");
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void assemblyIsAtomicAndRetainsPageOrder(GameTestHelper helper) {
|
||||
BookBinderBlockEntity binder = place(helper);
|
||||
@@ -237,6 +256,39 @@ public final class BookBinderGameTests {
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void forgedBinderActionsAndReplayedOutputCannotDuplicateBook(GameTestHelper helper) {
|
||||
BookBinderBlockEntity binder = place(helper);
|
||||
binder.setTitle("Single Assembly");
|
||||
binder.setItem(BookBinderBlockEntity.COVER, new ItemStack(Items.LEATHER));
|
||||
binder.setItem(BookBinderBlockEntity.LINK_PANEL, PageItem.createLinkPanelPage());
|
||||
var first = helper.makeMockServerPlayerInLevel();
|
||||
var second = helper.makeMockServerPlayerInLevel();
|
||||
for (var player : List.of(first, second)) player.moveTo(binder.getBlockPos().getX() + 0.5,
|
||||
binder.getBlockPos().getY() + 0.5, binder.getBlockPos().getZ() + 0.5);
|
||||
BookBinderMenu firstMenu = new BookBinderMenu(1, first.getInventory(), binder);
|
||||
BookBinderMenu secondMenu = new BookBinderMenu(2, second.getInventory(), binder);
|
||||
helper.assertFalse(firstMenu.clickMenuButton(first, BookBinderMenu.SELECT_BASE + BookBinderBlockEntity.PAGE_COUNT),
|
||||
"A fabricated page selection must be rejected");
|
||||
helper.assertFalse(firstMenu.clickMenuButton(first, BookBinderMenu.STRIP_CLICK_BASE
|
||||
+ BookBinderBlockEntity.PAGE_COUNT + 1), "A fabricated strip address must be rejected");
|
||||
helper.assertTrue(firstMenu.quickMoveStack(first, BookBinderMenu.RESULT).is(ModItems.DESCRIPTIVE_BOOK.get()),
|
||||
"The first viewer must receive the single assembled book");
|
||||
helper.assertTrue(secondMenu.quickMoveStack(second, BookBinderMenu.RESULT).isEmpty(),
|
||||
"Another viewer must not take a stale result");
|
||||
helper.assertTrue(firstMenu.quickMoveStack(first, BookBinderMenu.RESULT).isEmpty(),
|
||||
"Replaying the output request must not create a second book");
|
||||
int books = first.getInventory().items.stream().filter(stack -> stack.is(ModItems.DESCRIPTIVE_BOOK.get()))
|
||||
.mapToInt(ItemStack::getCount).sum();
|
||||
helper.assertValueEqual(books, 1, "Only one completed book may be emitted");
|
||||
helper.assertTrue(binder.getItem(BookBinderBlockEntity.COVER).isEmpty(), "Cover must be consumed once");
|
||||
helper.assertTrue(binder.getItem(BookBinderBlockEntity.LINK_PANEL).isEmpty(), "Panel must be consumed once");
|
||||
helper.getLevel().setBlock(binder.getBlockPos(), Blocks.AIR.defaultBlockState(), 3);
|
||||
helper.assertFalse(firstMenu.clickMenuButton(first, BookBinderMenu.IMPORT_NOTEBOOK),
|
||||
"A stale binder menu must reject packets after block removal");
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void legacyStripInsertsAndRemovesPagesInOrder(GameTestHelper helper) {
|
||||
BookBinderBlockEntity binder = place(helper);
|
||||
|
||||
@@ -12,7 +12,9 @@ import net.minecraft.gametest.framework.GameTestHelper;
|
||||
import net.minecraft.world.inventory.ClickType;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.entity.item.ItemEntity;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.neoforged.neoforge.gametest.GameTestHolder;
|
||||
import net.neoforged.neoforge.gametest.PrefixGameTestTemplate;
|
||||
|
||||
@@ -98,6 +100,14 @@ public final class InkMixerGameTests {
|
||||
"Shift-click must consume only one paper");
|
||||
helper.getLevel().setBlock(mixer.getBlockPos(), Blocks.AIR.defaultBlockState(), 3);
|
||||
helper.assertFalse(menu.stillValid(player), "Breaking the mixer must invalidate its menu");
|
||||
var drops = helper.getLevel().getEntitiesOfClass(ItemEntity.class,
|
||||
new AABB(mixer.getBlockPos()).inflate(2));
|
||||
int paper = drops.stream().filter(entity -> entity.getItem().is(Items.PAPER))
|
||||
.mapToInt(entity -> entity.getItem().getCount()).sum();
|
||||
int bottles = drops.stream().filter(entity -> entity.getItem().is(Items.GLASS_BOTTLE))
|
||||
.mapToInt(entity -> entity.getItem().getCount()).sum();
|
||||
helper.assertValueEqual(paper, 1, "Breaking the mixer must return unused paper once");
|
||||
helper.assertValueEqual(bottles, 1, "Breaking the mixer must return the consumed vial's bottle once");
|
||||
helper.succeed();
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,9 @@ package net.dnorberg.rivencraft.gametest;
|
||||
import com.mojang.serialization.JsonOps;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.dnorberg.rivencraft.book.AgeReference;
|
||||
import net.dnorberg.rivencraft.writing.OrderedWriting;
|
||||
import net.dnorberg.rivencraft.writing.PageContent;
|
||||
import net.dnorberg.rivencraft.writing.SymbolCatalog;
|
||||
@@ -71,8 +73,12 @@ public final class WritingDataGameTests {
|
||||
"0.3.7.2 bare-list writing must load into schema version 1 intact");
|
||||
|
||||
ItemStack oldPageFreeBook = new ItemStack(ModItems.DESCRIPTIVE_BOOK.get());
|
||||
AgeReference originalAge = new AgeReference(UUID.randomUUID(), "Legacy Page-Free Age");
|
||||
oldPageFreeBook.set(ModDataComponents.AGE_REFERENCE.get(), originalAge);
|
||||
CompoundTag saved = (CompoundTag) oldPageFreeBook.saveOptional(helper.getLevel().registryAccess());
|
||||
ItemStack restored = ItemStack.parseOptional(helper.getLevel().registryAccess(), saved);
|
||||
helper.assertValueEqual(restored.get(ModDataComponents.AGE_REFERENCE.get()), originalAge,
|
||||
"A page-free book must keep its existing Age identity");
|
||||
helper.assertValueEqual(restored.getOrDefault(ModDataComponents.BOOK_WRITING.get(), OrderedWriting.EMPTY),
|
||||
OrderedWriting.EMPTY, "Older page-free books must not require writing migration");
|
||||
helper.succeed();
|
||||
@@ -136,6 +142,10 @@ public final class WritingDataGameTests {
|
||||
);
|
||||
|
||||
ItemStack recovered = PageItem.createRecoveredPage(unknown);
|
||||
CompoundTag recoveredTag = (CompoundTag) recovered.saveOptional(helper.getLevel().registryAccess());
|
||||
ItemStack restoredRecovered = ItemStack.parseOptional(helper.getLevel().registryAccess(), recoveredTag);
|
||||
helper.assertValueEqual(PageItem.content(restoredRecovered).symbol().orElseThrow(), unknown,
|
||||
"A missing symbol must survive a full Page item reload");
|
||||
helper.assertFalse(PageItem.isUsableWriting(recovered),
|
||||
"An unresolved page must remain recoverable without becoming valid writing");
|
||||
try {
|
||||
|
||||
@@ -249,11 +249,40 @@ public final class WritingDeskGameTests {
|
||||
"First viewer should take the last available copy");
|
||||
helper.assertTrue(secondMenu.quickMoveStack(second, WritingDeskMenu.COPY_RESULT).isEmpty(),
|
||||
"Second viewer must not copy the already-consumed paper");
|
||||
helper.assertTrue(firstMenu.quickMoveStack(first, WritingDeskMenu.COPY_RESULT).isEmpty(),
|
||||
"Replaying the first viewer's output request must not make another page");
|
||||
helper.assertTrue(desk.getItem(WritingDeskBlockEntity.PAPER).isEmpty(),
|
||||
"Shared desk must spend paper only once");
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void forgedDeskSelectionsAndStaleMenuCannotCopy(GameTestHelper helper) {
|
||||
BlockPos pos = helper.absolutePos(new BlockPos(1, 1, 1));
|
||||
helper.getLevel().setBlock(pos, ModBlocks.WRITING_DESK.get().defaultBlockState(), 3);
|
||||
WritingDeskBlockEntity desk = (WritingDeskBlockEntity) helper.getLevel().getBlockEntity(pos);
|
||||
desk.setItem(0, PageItem.createLinkPanelPage());
|
||||
desk.setItem(WritingDeskBlockEntity.PAPER, new ItemStack(Items.PAPER));
|
||||
desk.setItem(WritingDeskBlockEntity.INK, new ItemStack(ModItems.INK_VIAL.get()));
|
||||
var player = helper.makeMockServerPlayerInLevel();
|
||||
player.moveTo(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5);
|
||||
WritingDeskMenu menu = new WritingDeskMenu(1, player.getInventory(), desk);
|
||||
helper.assertFalse(menu.clickMenuButton(player, WritingDeskMenu.CATALOG_BUTTON_BASE + 999),
|
||||
"A fabricated catalog index must be rejected");
|
||||
helper.assertFalse(menu.clickMenuButton(player, WritingDeskMenu.ALL_PAGE_BUTTON_BASE + 27),
|
||||
"A fabricated page address must be rejected");
|
||||
helper.assertFalse(menu.clickMenuButton(player, WritingDeskMenu.extractPageButtonId(0, 0,
|
||||
WritingDeskBlockEntity.PAPER)), "A page cannot be extracted into a material slot");
|
||||
helper.assertValueEqual(desk.getItem(WritingDeskBlockEntity.PAPER).getCount(), 1,
|
||||
"Forged actions must not spend paper");
|
||||
helper.assertTrue(desk.getItem(0).is(ModItems.PAGE.get()), "Forged actions must not remove the source");
|
||||
helper.getLevel().setBlock(pos, Blocks.AIR.defaultBlockState(), 3);
|
||||
helper.assertFalse(menu.clickMenuButton(player, 0), "A stale menu must reject selection packets");
|
||||
helper.assertTrue(menu.quickMoveStack(player, WritingDeskMenu.COPY_RESULT).isEmpty(),
|
||||
"A stale menu must reject output packets");
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void catalogAggregatesKnownSymbolsAcrossCollections(GameTestHelper helper) {
|
||||
BlockPos pos = helper.absolutePos(new BlockPos(1, 1, 1));
|
||||
|
||||
Reference in new issue
Block a user