Streamline GameTests and writing workstation logic
Build / build (push) Canceled after 0s

This commit is contained in:
dnorberg committed 2026-09-29 23:12:35 -04:00
1 parent 8246640f49
commit 114a6ee21e
19 files changed
+251 -111

No files matched your search

@@ -195,26 +195,19 @@ public final class BookBinderBlockEntity extends BlockEntity implements Containe
if (pages.isEmpty()) return ItemStack.EMPTY;
int end = nextPageIndex();
if (index < 0 || index > end) return ItemStack.EMPTY;
boolean link = false;
ItemStack linkPanel = ItemStack.EMPTY;
List<ItemStack> writing = new ArrayList<>();
for (ItemStack page : pages) {
if (page.isEmpty()) continue;
if (!page.is(ModItems.PAGE.get()) || page.getCount() != 1) return ItemStack.EMPTY;
if (PageItem.content(page).kind() == PageContent.Kind.LINK_PANEL) {
if (link || !items.get(LINK_PANEL).isEmpty()) return ItemStack.EMPTY;
link = true;
if (!linkPanel.isEmpty() || !items.get(LINK_PANEL).isEmpty()) return ItemStack.EMPTY;
linkPanel = page;
} else if (validPage(page)) writing.add(page);
else return ItemStack.EMPTY;
}
if (end + writing.size() > PAGE_COUNT || writing.isEmpty() && !link) return ItemStack.EMPTY;
if (link) {
for (ItemStack page : pages) {
if (!page.isEmpty() && PageItem.content(page).kind() == PageContent.Kind.LINK_PANEL) {
items.set(LINK_PANEL, page.copy());
break;
}
}
}
if (end + writing.size() > PAGE_COUNT || writing.isEmpty() && linkPanel.isEmpty()) return ItemStack.EMPTY;
if (!linkPanel.isEmpty()) items.set(LINK_PANEL, linkPanel.copy());
for (int current = end - 1; current >= index; current--)
items.set(FIRST_PAGE + current + writing.size(), items.get(FIRST_PAGE + current));
for (ItemStack page : writing) items.set(FIRST_PAGE + index++, page.copy());
@@ -1,6 +1,7 @@
package net.dnorberg.rivencraft.block.entity;
import net.dnorberg.rivencraft.item.InkVialItem;
import net.dnorberg.rivencraft.writing.InkVialExchange;
import net.dnorberg.rivencraft.registry.ModBlockEntities;
import net.dnorberg.rivencraft.registry.ModItems;
import net.dnorberg.rivencraft.writing.WritingInkService;
@@ -64,35 +65,21 @@ public final class InkMixerBlockEntity extends BlockEntity implements Container
worldPosition.getZ() + 0.5) <= 64.0;
}
private boolean hasBottleSpace() {
ItemStack bottles = items.get(BOTTLES);
return bottles.isEmpty() || bottles.is(Items.GLASS_BOTTLE)
&& bottles.getCount() < bottles.getMaxStackSize();
}
/** No server tick: refill only when ink, bottle space, or consumption changes. */
private void refillEmptyBasin() {
if (level == null || level.isClientSide || inkRemaining != 0 || !hasBottleSpace()) return;
ItemStack vial = items.get(INK);
if (!vial.is(ModItems.INK_VIAL.get())) return;
int amount = Math.clamp(InkVialItem.remaining(vial), 0, InkVialItem.CAPACITY);
if (amount == 0) return;
inkRemaining = amount;
vial.shrink(1);
if (vial.isEmpty()) items.set(INK, ItemStack.EMPTY);
ItemStack bottles = items.get(BOTTLES);
if (bottles.isEmpty()) items.set(BOTTLES, new ItemStack(Items.GLASS_BOTTLE));
else bottles.grow(1);
setChanged();
if (level != null && !level.isClientSide && inkRemaining == 0) {
int addedInk = InkVialExchange.exchange(items, INK, BOTTLES);
if (addedInk > 0) {
inkRemaining = addedInk;
setChanged();
}
}
}
public boolean canCreateLinkPanel() {
if (!items.get(PAPER).is(Items.PAPER)) return false;
int cost = WritingInkService.linkPanelCost();
if (inkRemaining >= cost) return true;
ItemStack vial = items.get(INK);
return vial.is(ModItems.INK_VIAL.get()) && hasBottleSpace()
&& inkRemaining + InkVialItem.remaining(vial) >= cost;
return InkVialExchange.canPay(items, INK, BOTTLES, inkRemaining, cost);
}
/** Commit inputs only when a virtual output is actually taken. */
@@ -100,13 +87,7 @@ public final class InkMixerBlockEntity extends BlockEntity implements Container
if (!canCreateLinkPanel()) return false;
int cost = WritingInkService.linkPanelCost();
if (inkRemaining < cost) {
ItemStack vial = items.get(INK);
inkRemaining += Math.clamp(InkVialItem.remaining(vial), 0, InkVialItem.CAPACITY);
vial.shrink(1);
if (vial.isEmpty()) items.set(INK, ItemStack.EMPTY);
ItemStack bottles = items.get(BOTTLES);
if (bottles.isEmpty()) items.set(BOTTLES, new ItemStack(Items.GLASS_BOTTLE));
else bottles.grow(1);
inkRemaining += InkVialExchange.exchange(items, INK, BOTTLES);
}
inkRemaining -= cost;
items.get(PAPER).shrink(1);
@@ -1,6 +1,7 @@
package net.dnorberg.rivencraft.block.entity;
import net.dnorberg.rivencraft.item.InkVialItem;
import net.dnorberg.rivencraft.writing.InkVialExchange;
import net.dnorberg.rivencraft.writing.WritingInkService;
import net.dnorberg.rivencraft.registry.ModBlockEntities;
import net.dnorberg.rivencraft.registry.ModItems;
@@ -92,26 +93,15 @@ public final class WritingDeskBlockEntity extends BlockEntity implements Contain
};
}
private boolean hasBottleSpace() {
ItemStack returned = items.get(RETURN);
return returned.isEmpty() || returned.is(Items.GLASS_BOTTLE)
&& returned.getCount() < returned.getMaxStackSize();
}
/** Consume one vial only when the empty meter can return its glass bottle. */
private void refillEmptyInkBar() {
if (level == null || level.isClientSide || inkRemaining != 0 || !hasBottleSpace()) return;
ItemStack vial = items.get(INK);
if (!vial.is(ModItems.INK_VIAL.get())) return;
int ink = Math.clamp(InkVialItem.remaining(vial), 0, InkVialItem.CAPACITY);
if (ink == 0) return;
inkRemaining = ink;
vial.shrink(1);
if (vial.isEmpty()) items.set(INK, ItemStack.EMPTY);
ItemStack returned = items.get(RETURN);
if (returned.isEmpty()) items.set(RETURN, new ItemStack(Items.GLASS_BOTTLE));
else returned.grow(1);
setChanged();
if (level != null && !level.isClientSide && inkRemaining == 0) {
int addedInk = InkVialExchange.exchange(items, INK, RETURN);
if (addedInk > 0) {
inkRemaining = addedInk;
setChanged();
}
}
}
/** The output is virtual; inputs are committed only after a result can be given to a player. */
@@ -119,23 +109,14 @@ public final class WritingDeskBlockEntity extends BlockEntity implements Contain
ItemStack paper = items.get(PAPER);
if (!canPlaceItem(PAPER, paper) || paper.isEmpty()) return false;
int cost = WritingInkService.symbolCopyCost();
if (inkRemaining >= cost) return true;
ItemStack vial = items.get(INK);
return vial.is(ModItems.INK_VIAL.get()) && inkRemaining + InkVialItem.remaining(vial) >= cost
&& hasBottleSpace();
return InkVialExchange.canPay(items, INK, RETURN, inkRemaining, cost);
}
public boolean consumeCopyMaterials() {
if (!canConsumeCopyMaterials()) return false;
int cost = WritingInkService.symbolCopyCost();
if (inkRemaining < cost) {
ItemStack vial = items.get(INK);
inkRemaining += Math.clamp(InkVialItem.remaining(vial), 0, InkVialItem.CAPACITY);
vial.shrink(1);
if (vial.isEmpty()) items.set(INK, ItemStack.EMPTY);
ItemStack returned = items.get(RETURN);
if (returned.isEmpty()) items.set(RETURN, new ItemStack(Items.GLASS_BOTTLE));
else returned.grow(1);
inkRemaining += InkVialExchange.exchange(items, INK, RETURN);
}
inkRemaining -= cost;
removeItem(PAPER, 1);
@@ -148,7 +129,6 @@ public final class WritingDeskBlockEntity extends BlockEntity implements Contain
refillEmptyInkBar();
}
@Override public void clearContent() {
items.clear();
items = NonNullList.withSize(SIZE, ItemStack.EMPTY);
inkRemaining = 0;
displayPage = ItemStack.EMPTY;
@@ -28,7 +28,7 @@ import net.minecraft.world.phys.AABB;
import net.neoforged.neoforge.gametest.GameTestHolder;
import net.neoforged.neoforge.gametest.PrefixGameTestTemplate;
@GameTestHolder(Rivencraft.MOD_ID)
@GameTestHolder("rivencraft_archive_binder")
@PrefixGameTestTemplate(false)
public final class BookBinderGameTests {
private BookBinderGameTests() {}
@@ -74,7 +74,6 @@ public final class BookBinderGameTests {
helper.succeed();
}
@GameTest(template = "empty")
public static void missingSymbolPageSurvivesReloadButCannotBeAssembled(GameTestHelper helper) {
BookBinderBlockEntity binder = place(helper);
binder.setTitle("Recoverable Writing");
@@ -93,7 +92,6 @@ public final class BookBinderGameTests {
helper.succeed();
}
@GameTest(template = "empty")
public static void assemblyIsAtomicAndRetainsPageOrder(GameTestHelper helper) {
BookBinderBlockEntity binder = place(helper);
binder.setTitle("A New Age");
@@ -256,7 +254,6 @@ public final class BookBinderGameTests {
helper.succeed();
}
@GameTest(template = "empty")
public static void forgedBinderActionsAndReplayedOutputCannotDuplicateBook(GameTestHelper helper) {
BookBinderBlockEntity binder = place(helper);
binder.setTitle("Single Assembly");
@@ -0,0 +1,94 @@
package net.dnorberg.rivencraft.gametest;
import net.dnorberg.rivencraft.Rivencraft;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestHelper;
import net.neoforged.neoforge.gametest.GameTestHolder;
import net.neoforged.neoforge.gametest.PrefixGameTestTemplate;
/** Small default smoke suite; the detailed milestone regressions are opt-in. */
@GameTestHolder(Rivencraft.MOD_ID)
@PrefixGameTestTemplate(false)
public final class CoreGameTests {
private CoreGameTests() {}
@GameTest(template = "empty")
public static void previewBackendSelection(GameTestHelper helper) {
LinkingGameTests.previewBackendSelectionCoversOptionalDependencyMatrix(helper);
}
@GameTest(template = "empty")
public static void linkTargetPersistence(GameTestHelper helper) {
LinkingGameTests.linkTargetSurvivesItemSerialization(helper);
}
@GameTest(template = "empty")
public static void deterministicAgeDefinition(GameTestHelper helper) {
LinkingGameTests.ageDefinitionIsCreatedOnceWithDeterministicData(helper);
}
@GameTest(template = "empty")
public static void blockedLinkDestination(GameTestHelper helper) {
LinkingGameTests.travelRejectsBlockedDestination(helper);
}
@GameTest(template = "empty")
public static void legacyWritingAndPageFreeBooks(GameTestHelper helper) {
WritingDataGameTests.oldBareListWritingAndPageFreeBooksRemainReadable(helper);
}
@GameTest(template = "empty")
public static void pagePersistenceAndUnknownSymbols(GameTestHelper helper) {
WritingDataGameTests.pageItemPreservesContentAndRejectsForgedUnknownSymbols(helper);
}
@GameTest(template = "empty")
public static void notebookPersistence(GameTestHelper helper) {
WritingDataGameTests.notebookContentsPreserveOrderGapsNamesAndUnknownPages(helper);
}
@GameTest(template = "empty")
public static void deskCopyTransaction(GameTestHelper helper) {
WritingDeskGameTests.takingCopySpendsPaperAndOneInkThenReturnsGlassBottle(helper);
}
@GameTest(template = "empty")
public static void deskRejectsForgedAndStaleActions(GameTestHelper helper) {
WritingDeskGameTests.forgedDeskSelectionsAndStaleMenuCannotCopy(helper);
}
@GameTest(template = "empty")
public static void twoBlockDeskDropsOnce(GameTestHelper helper) {
WritingDeskGameTests.twoBlockDeskSharesOneInventoryAndBreaksTogether(helper);
}
@GameTest(template = "empty")
public static void oldDeskMigration(GameTestHelper helper) {
WritingDeskGameTests.oldDeskInventoryMigratesAndAll88TabsPersist(helper);
}
@GameTest(template = "empty")
public static void binderKeepsMissingSymbolsRecoverable(GameTestHelper helper) {
BookBinderGameTests.missingSymbolPageSurvivesReloadButCannotBeAssembled(helper);
}
@GameTest(template = "empty")
public static void binderAssemblyTransaction(GameTestHelper helper) {
BookBinderGameTests.assemblyIsAtomicAndRetainsPageOrder(helper);
}
@GameTest(template = "empty")
public static void binderRejectsForgedAndReplayedActions(GameTestHelper helper) {
BookBinderGameTests.forgedBinderActionsAndReplayedOutputCannotDuplicateBook(helper);
}
@GameTest(template = "empty")
public static void mixerOutputTransaction(GameTestHelper helper) {
InkMixerGameTests.linkPanelRequiresTakingOutputAndPersistsInk(helper);
}
@GameTest(template = "empty")
public static void linkingBookRecipe(GameTestHelper helper) {
LinkingBookRecipeGameTests.legacyRecipeRequiresExactlyLeatherAndLinkPanel(helper);
}
}
@@ -1,6 +1,5 @@
package net.dnorberg.rivencraft.gametest;
import net.dnorberg.rivencraft.Rivencraft;
import net.dnorberg.rivencraft.block.entity.InkMixerBlockEntity;
import net.dnorberg.rivencraft.menu.InkMixerMenu;
import net.dnorberg.rivencraft.registry.ModBlocks;
@@ -18,7 +17,7 @@ import net.minecraft.world.phys.AABB;
import net.neoforged.neoforge.gametest.GameTestHolder;
import net.neoforged.neoforge.gametest.PrefixGameTestTemplate;
@GameTestHolder(Rivencraft.MOD_ID)
@GameTestHolder("rivencraft_archive_mixer")
@PrefixGameTestTemplate(false)
public final class InkMixerGameTests {
private InkMixerGameTests() {}
@@ -29,7 +28,6 @@ public final class InkMixerGameTests {
return (InkMixerBlockEntity) helper.getLevel().getBlockEntity(pos);
}
@GameTest(template = "empty")
public static void linkPanelRequiresTakingOutputAndPersistsInk(GameTestHelper helper) {
InkMixerBlockEntity mixer = place(helper);
mixer.setItem(InkMixerBlockEntity.PAPER, new ItemStack(Items.PAPER, 2));
@@ -19,12 +19,11 @@ import net.minecraft.world.item.crafting.RecipeType;
import net.neoforged.neoforge.gametest.GameTestHolder;
import net.neoforged.neoforge.gametest.PrefixGameTestTemplate;
@GameTestHolder(Rivencraft.MOD_ID)
@GameTestHolder("rivencraft_archive_recipe")
@PrefixGameTestTemplate(false)
public final class LinkingBookRecipeGameTests {
private LinkingBookRecipeGameTests() {}
@GameTest(template = "empty")
public static void legacyRecipeRequiresExactlyLeatherAndLinkPanel(GameTestHelper helper) {
LinkingBookRecipe recipe = new LinkingBookRecipe(CraftingBookCategory.MISC);
ItemStack panel = PageItem.createLinkPanelPage();
@@ -34,7 +34,7 @@ import net.neoforged.neoforge.gametest.GameTestHolder;
import net.neoforged.neoforge.gametest.PrefixGameTestTemplate;
/** Server-side regression coverage for persistent linking-book data and travel safety. */
@GameTestHolder(Rivencraft.MOD_ID)
@GameTestHolder("rivencraft_archive_linking")
@PrefixGameTestTemplate(false)
public final class LinkingGameTests {
private static final BlockPos ORIGIN = new BlockPos(0, 1, 0);
@@ -42,7 +42,6 @@ public final class LinkingGameTests {
private LinkingGameTests() {
}
@GameTest(template = "empty")
public static void previewBackendSelectionCoversOptionalDependencyMatrix(GameTestHelper helper) {
assertPreviewBackend(helper, RivencraftClientConfig.PreviewMode.AUTO, true, true,
PreviewBackendSelector.Backend.IMMERSIVE_PORTALS);
@@ -127,7 +126,6 @@ public final class LinkingGameTests {
helper.succeed();
}
@GameTest(template = "empty")
public static void linkTargetSurvivesItemSerialization(GameTestHelper helper) {
LinkTarget expected = new LinkTarget(
GlobalPos.of(helper.getLevel().dimension(), helper.absolutePos(ORIGIN)),
@@ -181,7 +179,6 @@ public final class LinkingGameTests {
helper.succeed();
}
@GameTest(template = "empty")
public static void ageDefinitionIsCreatedOnceWithDeterministicData(GameTestHelper helper) {
UUID ageId = UUID.randomUUID();
String requestedName = "Test Age " + ageId;
@@ -666,7 +663,6 @@ public final class LinkingGameTests {
helper.succeed();
}
@GameTest(template = "empty")
public static void travelRejectsBlockedDestination(GameTestHelper helper) {
BlockPos absoluteOrigin = helper.absolutePos(ORIGIN);
for (BlockPos position : BlockPos.betweenClosed(
@@ -4,7 +4,6 @@ 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;
@@ -31,7 +30,7 @@ import net.neoforged.neoforge.gametest.GameTestHolder;
import net.neoforged.neoforge.gametest.PrefixGameTestTemplate;
/** Acceptance coverage for Milestone 4's persistent writing contracts. */
@GameTestHolder(Rivencraft.MOD_ID)
@GameTestHolder("rivencraft_archive_writing")
@PrefixGameTestTemplate(false)
public final class WritingDataGameTests {
private WritingDataGameTests() {
@@ -62,7 +61,6 @@ public final class WritingDataGameTests {
helper.succeed();
}
@GameTest(template = "empty")
public static void oldBareListWritingAndPageFreeBooksRemainReadable(GameTestHelper helper) {
OrderedWriting writing = new OrderedWriting(List.of(
WritingEntry.linkPanel(), WritingEntry.symbol(reference("example:removed_symbol"))));
@@ -126,7 +124,6 @@ public final class WritingDataGameTests {
helper.succeed();
}
@GameTest(template = "empty")
public static void pageItemPreservesContentAndRejectsForgedUnknownSymbols(GameTestHelper helper) {
SymbolReference known = reference("rivencraft:terrain/standard");
SymbolReference unknown = reference("example:forged_symbol");
@@ -156,7 +153,6 @@ public final class WritingDataGameTests {
}
}
@GameTest(template = "empty")
public static void notebookContentsPreserveOrderGapsNamesAndUnknownPages(GameTestHelper helper) {
ItemStack first = PageItem.createSymbolPage(reference("rivencraft:terrain/standard"));
first.set(net.minecraft.core.component.DataComponents.CUSTOM_NAME,
@@ -1,7 +1,6 @@
package net.dnorberg.rivencraft.gametest;
import java.util.List;
import net.dnorberg.rivencraft.Rivencraft;
import net.dnorberg.rivencraft.block.entity.WritingDeskBlockEntity;
import net.dnorberg.rivencraft.block.WritingDeskBlock;
import net.dnorberg.rivencraft.menu.WritingDeskMenu;
@@ -26,7 +25,7 @@ import net.minecraft.world.phys.AABB;
import net.neoforged.neoforge.gametest.GameTestHolder;
import net.neoforged.neoforge.gametest.PrefixGameTestTemplate;
@GameTestHolder(Rivencraft.MOD_ID)
@GameTestHolder("rivencraft_archive_desk")
@PrefixGameTestTemplate(false)
public final class WritingDeskGameTests {
private WritingDeskGameTests() {}
@@ -94,7 +93,6 @@ public final class WritingDeskGameTests {
helper.succeed();
}
@GameTest(template = "empty")
public static void takingCopySpendsPaperAndOneInkThenReturnsGlassBottle(GameTestHelper helper) {
BlockPos pos = helper.absolutePos(new BlockPos(1, 1, 1));
helper.getLevel().setBlock(pos, ModBlocks.WRITING_DESK.get().defaultBlockState(), 3);
@@ -256,7 +254,6 @@ public final class WritingDeskGameTests {
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);
@@ -339,7 +336,6 @@ public final class WritingDeskGameTests {
helper.succeed();
}
@GameTest(template = "empty")
public static void twoBlockDeskSharesOneInventoryAndBreaksTogether(GameTestHelper helper) {
BlockPos pos = helper.absolutePos(new BlockPos(1, 1, 1));
var block = ModBlocks.WRITING_DESK.get();
@@ -462,7 +458,6 @@ public final class WritingDeskGameTests {
helper.succeed();
}
@GameTest(template = "empty")
public static void oldDeskInventoryMigratesAndAll88TabsPersist(GameTestHelper helper) {
BlockPos pos = helper.absolutePos(new BlockPos(1, 1, 1));
var registries = helper.getLevel().registryAccess();
@@ -166,15 +166,12 @@ public final class WritingDeskMenu extends AbstractContainerMenu {
selectedPage = page;
selectedSymbol = null;
} else if (id >= CATALOG_BUTTON_BASE && id < CATALOG_BUTTON_BASE + catalog.size()) {
if (!catalog.equals(WritingDeskCatalog.available(blockEntity))) {
refreshCatalog();
List<SymbolDisplayMetadata> available = WritingDeskCatalog.available(blockEntity);
if (!catalog.equals(available)) {
refreshCatalog(available);
return false;
}
ResourceLocation requested = catalog.get(id - CATALOG_BUTTON_BASE).id();
if (!WritingDeskCatalog.available(blockEntity).stream().anyMatch(entry -> entry.id().equals(requested))) {
return false;
}
selectedSymbol = requested;
selectedSymbol = available.get(id - CATALOG_BUTTON_BASE).id();
} else return false;
refreshOutput();
return true;
@@ -390,7 +387,11 @@ public final class WritingDeskMenu extends AbstractContainerMenu {
private void refreshCatalog() {
if (clientSide || blockEntity == null) return;
List<SymbolDisplayMetadata> current = WritingDeskCatalog.available(blockEntity);
refreshCatalog(WritingDeskCatalog.available(blockEntity));
}
private void refreshCatalog(List<SymbolDisplayMetadata> current) {
if (clientSide || blockEntity == null) return;
if (!current.equals(catalog)) {
catalog = current;
if (serverViewer != null && serverViewer.containerMenu == this) {
@@ -0,0 +1,43 @@
package net.dnorberg.rivencraft.writing;
import net.dnorberg.rivencraft.item.InkVialItem;
import net.dnorberg.rivencraft.registry.ModItems;
import net.minecraft.core.NonNullList;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
/** Shared, server-side vial-to-meter exchange for the Desk and Ink Mixer. */
public final class InkVialExchange {
private InkVialExchange() {}
public static boolean canPay(NonNullList<ItemStack> items, int inkSlot, int bottleSlot,
int storedInk, int cost) {
return storedInk >= cost || canExchange(items, inkSlot, bottleSlot)
&& storedInk + vialInk(items.get(inkSlot)) >= cost;
}
/** Returns added ink; zero means no inputs were changed. */
public static int exchange(NonNullList<ItemStack> items, int inkSlot, int bottleSlot) {
if (!canExchange(items, inkSlot, bottleSlot)) return 0;
ItemStack vial = items.get(inkSlot);
int addedInk = vialInk(vial);
vial.shrink(1);
if (vial.isEmpty()) items.set(inkSlot, ItemStack.EMPTY);
ItemStack bottles = items.get(bottleSlot);
if (bottles.isEmpty()) items.set(bottleSlot, new ItemStack(Items.GLASS_BOTTLE));
else bottles.grow(1);
return addedInk;
}
private static boolean canExchange(NonNullList<ItemStack> items, int inkSlot, int bottleSlot) {
ItemStack vial = items.get(inkSlot);
ItemStack bottles = items.get(bottleSlot);
return vial.is(ModItems.INK_VIAL.get()) && vialInk(vial) > 0
&& (bottles.isEmpty() || bottles.is(Items.GLASS_BOTTLE)
&& bottles.getCount() < bottles.getMaxStackSize());
}
private static int vialInk(ItemStack vial) {
return Math.clamp(InkVialItem.remaining(vial), 0, InkVialItem.CAPACITY);
}
}
@@ -2,9 +2,9 @@ package net.dnorberg.rivencraft.writing;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import net.dnorberg.rivencraft.block.entity.WritingDeskBlockEntity;
import net.dnorberg.rivencraft.item.PageItem;
import net.dnorberg.rivencraft.registry.ModDataComponents;
@@ -20,7 +20,7 @@ public final class WritingDeskCatalog {
private WritingDeskCatalog() {}
public static List<SymbolDisplayMetadata> available(WritingDeskBlockEntity desk) {
Map<ResourceLocation, SymbolDisplayMetadata> unique = new TreeMap<>();
Map<ResourceLocation, SymbolDisplayMetadata> unique = new HashMap<>();
for (int slot = 0; slot < WritingDeskBlockEntity.COLLECTIONS; slot++) {
ItemStack collection = desk.getItem(slot);
if (collection.is(ModItems.PAGE.get())) add(unique, collection);