diff --git a/NOTICE b/NOTICE index f52a95c..2d85d7f 100644 --- a/NOTICE +++ b/NOTICE @@ -12,6 +12,8 @@ The Notebook page atlas is copied from Legacy's assets/mystcraft/gui/bookui_page under the same license. The Writing Desk item texture is copied from Legacy's assets/mystcraft/textures/items/writingdesk.png under the same license. +The Writing Desk block texture is copied from Legacy's +assets/mystcraft/textures/entity/desk.png under the same license. The initial build layout is derived from the official NeoForge 1.21.1 ModDevGradle MDK: https://github.com/NeoForgeMDKs/MDK-1.21.1-ModDevGradle diff --git a/README.md b/README.md index 75df96d..b38667e 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ have not been ported yet. Milestone 4 now includes stackable blank Pages plus server-created symbol, unresolved-symbol, and baseline link-panel states. Page visuals follow the legacy runtime-composition rules. Craft an Ink Vial from two black dye, a glass -bottle, and a water bucket; the bucket is returned. Hold a blank Page in one +bottle, and a water bucket; the bucket is returned. Hold vanilla paper in one hand and use the Ink Vial in the other to prepare a baseline Link Panel Page. Each vial holds 16 ink units, and the default link-panel cost is two units. The spent vial returns a glass bottle. Server owners can adjust the symbol-copy @@ -25,12 +25,13 @@ Its four collection tabs, paper input, ink vial input, empty glass-bottle return 16-unit ink meter, and inventory persist through world saves; breaking it drops stored contents. The desk screen is a functional foundation styled after Legacy's left tabs, central writing surface, -and right inventory. Put vanilla paper (or a blank Rivencraft Page) in the +and right inventory. Put vanilla paper in the upper-left slot and Ink Vials in the upper-right; a copy appears in the lower-left result slot when the selected page is valid. Taking it consumes one paper and, by default, one ink. An empty meter consumes a vial to refill and -returns a vanilla glass bottle to the lower-right slot. Writing new symbols -from a catalog remains future work. +returns a vanilla glass bottle to the lower-right slot. The ALL browser searches +and filters symbols held in the desk's loose pages and Notebooks; selection +is validated against the server's current symbol catalog. Selecting a stored page on the desk surface shows its Legacy-proportioned portrait preview and name above the player inventory. diff --git a/docs/ASSET_PROVENANCE.md b/docs/ASSET_PROVENANCE.md index b16fa40..5ca85d7 100644 --- a/docs/ASSET_PROVENANCE.md +++ b/docs/ASSET_PROVENANCE.md @@ -9,6 +9,7 @@ project's GNU LGPL v3 license. Rivencraft itself uses the same license. | `assets/rivencraft/textures/item/page.png` | `assets/mystcraft/textures/items/page_background.png` | `9bc8ddc061845df0cd5ea47f4fdef773cff786ae` | Namespace/path rename only | | `assets/rivencraft/textures/gui/notebook_pages.png` | `assets/mystcraft/gui/bookui_pagel.png` | `9bc8ddc061845df0cd5ea47f4fdef773cff786ae` | Namespace/path rename only; Notebook draws the native 30×40 page at atlas coordinates `(156, 0)` | | `assets/rivencraft/textures/item/writing_desk.png` | `assets/mystcraft/textures/items/writingdesk.png` | `9bc8ddc061845df0cd5ea47f4fdef773cff786ae` | Namespace/path rename only | +| `assets/rivencraft/textures/block/writing_desk.png` | `assets/mystcraft/textures/entity/desk.png` | `9bc8ddc061845df0cd5ea47f4fdef773cff786ae` | Namespace/path rename only; single-block model adapts the desk geometry and UV regions from `ModelWritingDesk` | | `assets/rivencraft/textures/item/linking_book_unwritten.png` | `assets/mystcraft/textures/items/unlinked.png` | `9bc8ddc061845df0cd5ea47f4fdef773cff786ae` | Namespace/path rename only | | `assets/rivencraft/textures/item/page_link_panel.png` | `page_background.png` and `PageBuilder.BasicPageSprite` | `9bc8ddc061845df0cd5ea47f4fdef773cff786ae` | Reproduces the legacy 160 px scale, black panel at `(25,30)`–`(135,75)`, and 128 px resample | | `assets/rivencraft/textures/item/page_symbol*.png` and `page_unresolved.png` | `page_background.png`, `textures/symbolcomponents.png`, `WordData`, `ModSymbols`, and `PageBuilder.PageSprite` | `9bc8ddc061845df0cd5ea47f4fdef773cff786ae` | Static 1.21.1 renditions of the legacy runtime-composed four-word symbol pages; `normal_time` uses the legacy Full Length poem | diff --git a/docs/WRITING_DATA.md b/docs/WRITING_DATA.md index cf6f33f..5bc9db9 100644 --- a/docs/WRITING_DATA.md +++ b/docs/WRITING_DATA.md @@ -53,8 +53,8 @@ unresolved symbol, and baseline link panel. Symbol and unresolved-symbol pages retain their namespaced symbol reference; unresolved pages can be inspected and removed but are rejected as usable writing until their data pack returns. -Only server-side writing operations may turn a blank page into a known symbol -page or link panel. The shared `PageItem` creation and validation gates reject +Only server-side writing operations may create a known symbol +page or link panel from vanilla paper. The shared `PageItem` creation and validation gates reject new pages for identifiers absent from the server catalog. Later Writing Desk and Book Binder packets must call these gates rather than accepting page contents supplied by a client. @@ -82,9 +82,9 @@ used to craft another. The animated item texture comes from Mystcraft Legacy. The server config sets ink costs independently for symbol copying (default one unit) and baseline link-panel preparation (default two units). To prepare a -panel, hold a blank Page in one hand and use an Ink Vial in the other. The -server checks the held items, blank state, and sufficient ink before consuming -anything. A stack of blank Pages yields exactly one Link Panel Page per use; +panel, hold vanilla paper in one hand and use an Ink Vial in the other. The +server checks the held items and sufficient ink before consuming +anything. A stack of paper yields exactly one Link Panel Page per use; if the inventory is full, the result is dropped instead of deleted. Symbol copying is implemented by the Writing Desk: it previews a copy of the selected resolved page, then spends paper and ink only when that copy is taken. The diff --git a/gradle.properties b/gradle.properties index 42041d1..e380cad 100644 --- a/gradle.properties +++ b/gradle.properties @@ -35,7 +35,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.3.5.2 +mod_version=0.3.6.0 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/block/WritingDeskBlock.java b/src/main/java/net/dnorberg/rivencraft/block/WritingDeskBlock.java index fe611d8..76d74ca 100644 --- a/src/main/java/net/dnorberg/rivencraft/block/WritingDeskBlock.java +++ b/src/main/java/net/dnorberg/rivencraft/block/WritingDeskBlock.java @@ -3,6 +3,7 @@ package net.dnorberg.rivencraft.block; import com.mojang.serialization.MapCodec; import net.dnorberg.rivencraft.block.entity.WritingDeskBlockEntity; import net.dnorberg.rivencraft.menu.WritingDeskMenu; +import net.dnorberg.rivencraft.writing.WritingDeskCatalog; import net.minecraft.core.BlockPos; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerPlayer; @@ -48,7 +49,10 @@ public final class WritingDeskBlock extends BaseEntityBlock { serverPlayer.openMenu(new SimpleMenuProvider( (id, inventory, ignored) -> new WritingDeskMenu(id, inventory, desk), Component.translatable("container.rivencraft.writing_desk")), - buffer -> buffer.writeBlockPos(pos)); + buffer -> { + buffer.writeBlockPos(pos); + WritingDeskCatalog.write(buffer, WritingDeskCatalog.available(desk)); + }); } return InteractionResult.sidedSuccess(level.isClientSide); } diff --git a/src/main/java/net/dnorberg/rivencraft/block/entity/WritingDeskBlockEntity.java b/src/main/java/net/dnorberg/rivencraft/block/entity/WritingDeskBlockEntity.java index 28958ca..d817a95 100644 --- a/src/main/java/net/dnorberg/rivencraft/block/entity/WritingDeskBlockEntity.java +++ b/src/main/java/net/dnorberg/rivencraft/block/entity/WritingDeskBlockEntity.java @@ -1,7 +1,6 @@ package net.dnorberg.rivencraft.block.entity; import net.dnorberg.rivencraft.item.InkVialItem; -import net.dnorberg.rivencraft.item.PageItem; import net.dnorberg.rivencraft.writing.WritingInkService; import net.dnorberg.rivencraft.registry.ModBlockEntities; import net.dnorberg.rivencraft.registry.ModItems; @@ -68,9 +67,7 @@ public final class WritingDeskBlockEntity extends BlockEntity implements Contain return switch (slot) { case INK -> stack.is(ModItems.INK_VIAL.get()); case RETURN -> false; - case PAPER -> stack.is(Items.PAPER) - || stack.is(ModItems.PAGE.get()) && PageItem.content(stack).kind() - == net.dnorberg.rivencraft.writing.PageContent.Kind.BLANK; + case PAPER -> stack.is(Items.PAPER); case LEGACY_OUTPUT -> false; default -> false; }; diff --git a/src/main/java/net/dnorberg/rivencraft/client/ClientWritingDeskCatalog.java b/src/main/java/net/dnorberg/rivencraft/client/ClientWritingDeskCatalog.java new file mode 100644 index 0000000..ba82635 --- /dev/null +++ b/src/main/java/net/dnorberg/rivencraft/client/ClientWritingDeskCatalog.java @@ -0,0 +1,20 @@ +package net.dnorberg.rivencraft.client; + +import net.dnorberg.rivencraft.menu.WritingDeskMenu; +import net.dnorberg.rivencraft.network.WritingDeskCatalogPayload; +import net.minecraft.client.Minecraft; +import net.neoforged.neoforge.network.handling.IPayloadContext; + +public final class ClientWritingDeskCatalog { + private ClientWritingDeskCatalog() {} + + public static void handle(WritingDeskCatalogPayload payload, IPayloadContext context) { + context.enqueueWork(() -> { + var player = Minecraft.getInstance().player; + if (player != null && player.containerMenu instanceof WritingDeskMenu menu + && menu.containerId == payload.containerId()) { + menu.setCatalog(payload.symbols()); + } + }); + } +} diff --git a/src/main/java/net/dnorberg/rivencraft/client/screen/NotebookScreen.java b/src/main/java/net/dnorberg/rivencraft/client/screen/NotebookScreen.java index 979cc55..3c5f38b 100644 --- a/src/main/java/net/dnorberg/rivencraft/client/screen/NotebookScreen.java +++ b/src/main/java/net/dnorberg/rivencraft/client/screen/NotebookScreen.java @@ -276,7 +276,11 @@ public final class NotebookScreen extends AbstractContainerScreen if (content.kind() == net.dnorberg.rivencraft.writing.PageContent.Kind.UNRESOLVED_SYMBOL) { return UNRESOLVED_SYMBOL; } - return switch (content.symbol().orElseThrow().id().toString()) { + return symbolTexture(content.symbol().orElseThrow().id()); + } + + static ResourceLocation symbolTexture(ResourceLocation id) { + return switch (id.toString()) { case "rivencraft:terrain/standard" -> STANDARD_TERRAIN; case "rivencraft:biome/native" -> NATIVE_BIOMES; case "rivencraft:lighting/normal" -> NORMAL_LIGHTING; diff --git a/src/main/java/net/dnorberg/rivencraft/client/screen/WritingDeskScreen.java b/src/main/java/net/dnorberg/rivencraft/client/screen/WritingDeskScreen.java index 4a7a05b..314744f 100644 --- a/src/main/java/net/dnorberg/rivencraft/client/screen/WritingDeskScreen.java +++ b/src/main/java/net/dnorberg/rivencraft/client/screen/WritingDeskScreen.java @@ -1,13 +1,18 @@ package net.dnorberg.rivencraft.client.screen; import java.util.List; +import java.util.Locale; +import java.util.Objects; import net.dnorberg.rivencraft.Rivencraft; import net.dnorberg.rivencraft.item.PageItem; import net.dnorberg.rivencraft.menu.WritingDeskMenu; import net.dnorberg.rivencraft.registry.ModDataComponents; import net.dnorberg.rivencraft.registry.ModItems; import net.dnorberg.rivencraft.writing.NotebookContents; +import net.dnorberg.rivencraft.writing.SymbolDisplayMetadata; import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.EditBox; import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; @@ -21,6 +26,11 @@ public final class WritingDeskScreen extends AbstractContainerScreen { + allMode = true; + scrollRows = 0; + }).bounds(leftPos + 48, topPos - 18, 29, 18).build()); + categoryButton = addRenderableWidget(Button.builder(Component.literal("All types"), button -> { + List categories = menu.catalog().stream() + .map(SymbolDisplayMetadata::category).distinct().sorted().toList(); + int next = selectedCategory == null ? 0 : categories.indexOf(selectedCategory) + 1; + selectedCategory = next >= categories.size() ? null : categories.get(next); + categoryButton.setMessage(Component.literal(selectedCategory == null + ? "All types" : selectedCategory.getPath())); + allMode = true; + scrollRows = 0; + }).bounds(leftPos + 79, topPos - 18, 66, 18).build()); + search = new EditBox(font, leftPos + 147, topPos - 18, 69, 18, + Component.translatable("screen.rivencraft.writing_desk.search")); + search.setMaxLength(64); + search.setHint(Component.translatable("screen.rivencraft.writing_desk.search")); + search.setResponder(value -> { + allMode = true; + scrollRows = 0; + }); + addRenderableWidget(search); + } + + private List filteredCatalog() { + String query = search == null ? "" : search.getValue().trim().toLowerCase(Locale.ROOT); + return menu.catalog().stream() + .filter(entry -> selectedCategory == null || selectedCategory.equals(entry.category())) + .filter(entry -> query.isEmpty() + || entry.id().toString().toLowerCase(Locale.ROOT).contains(query) + || Component.translatable(entry.nameTranslationKey()).getString() + .toLowerCase(Locale.ROOT).contains(query)) + .toList(); + } + @Override public void renderBackground(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { // Legacy leaves the world visible around the desk instead of dimming it. } @@ -94,6 +142,10 @@ public final class WritingDeskScreen extends AbstractContainerScreen pages = pages(); if (lastCollection != menu.activeCollection()) { lastCollection = menu.activeCollection(); @@ -124,6 +176,41 @@ public final class WritingDeskScreen extends AbstractContainerScreen filtered = filteredCatalog(); + if (filtered.isEmpty()) { + graphics.drawString(font, Component.translatable("screen.rivencraft.writing_desk.no_symbols"), + leftPos + 52, topPos + 24, 0xFFC4B8A3, false); + } + for (int i = scrollRows * 5; i < filtered.size() && i < scrollRows * 5 + 15; i++) { + SymbolDisplayMetadata entry = filtered.get(i); + int x = leftPos + 48 + (i % 5) * 31; + int y = topPos + 18 + (i / 5 - scrollRows) * 41; + drawSymbolPage(graphics, entry.id(), x, y); + if (Objects.equals(entry.id(), selectedSymbol)) { + graphics.renderOutline(x, y, 30, 40, 0xFFFFD061); + } + } + SymbolDisplayMetadata selected = menu.catalog().stream() + .filter(entry -> Objects.equals(entry.id(), selectedSymbol)).findFirst().orElse(null); + if (selected == null) return; + graphics.pose().pushPose(); + graphics.pose().translate(leftPos + 294, topPos + 12, 0); + graphics.pose().scale(1.35F, 1.35F, 1.0F); + drawSymbolPage(graphics, selected.id(), 0, 0); + graphics.pose().popPose(); + String title = font.plainSubstrByWidth( + Component.translatable(selected.nameTranslationKey()).getString(), 112); + graphics.fill(leftPos + 251, topPos + 64, leftPos + 365, topPos + 77, 0xFF202020); + graphics.drawString(font, title, leftPos + 254, topPos + 67, 0xFFE0E0E0, false); + } + + private void drawSymbolPage(GuiGraphics graphics, ResourceLocation id, int x, int y) { + graphics.blit(PAGE_ATLAS, x, y, 30, 40, 156, 0, 30, 40, 256, 256); + graphics.blit(NotebookScreen.symbolTexture(id), x + 1, y + 6, + 28, 28, 16, 16, 96, 96, 128, 128); + } + private void drawPage(GuiGraphics graphics, ItemStack page, int x, int y) { graphics.blit(PAGE_ATLAS, x, y, 30, 40, 156, 0, 30, 40, 256, 256); if (page.isEmpty()) return; @@ -143,12 +230,24 @@ public final class WritingDeskScreen extends AbstractContainerScreen pages = pages(); - if (x % 31 < 30 && y % 41 < 40 && index < pages.size() - && !pages.get(index).isEmpty()) { - selectedPage = index; - if (minecraft != null && minecraft.gameMode != null) { - minecraft.gameMode.handleInventoryButtonClick(menu.containerId, 16 + index); + if (allMode) { + List filtered = filteredCatalog(); + if (x % 31 < 30 && y % 41 < 40 && index < filtered.size()) { + SymbolDisplayMetadata entry = filtered.get(index); + selectedSymbol = entry.id(); + int serverIndex = menu.catalog().indexOf(entry); + if (serverIndex >= 0 && minecraft != null && minecraft.gameMode != null) { + minecraft.gameMode.handleInventoryButtonClick(menu.containerId, 100 + serverIndex); + } + } + } else { + List pages = pages(); + if (x % 31 < 30 && y % 41 < 40 && index < pages.size() + && !pages.get(index).isEmpty()) { + selectedPage = index; + if (minecraft != null && minecraft.gameMode != null) { + minecraft.gameMode.handleInventoryButtonClick(menu.containerId, 16 + index); + } } } return true; @@ -158,6 +257,8 @@ public final class WritingDeskScreen extends AbstractContainerScreen= top && mouseY < top + 32) { + allMode = false; + selectedSymbol = null; if (minecraft != null && minecraft.gameMode != null) { minecraft.gameMode.handleInventoryButtonClick(menu.containerId, i); } @@ -171,10 +272,23 @@ public final class WritingDeskScreen extends AbstractContainerScreen= leftPos + 48 && mouseX < leftPos + 203 && mouseY >= topPos + 18 && mouseY < topPos + 141) { - int rows = (pages().size() + 4) / 5; + int rows = ((allMode ? filteredCatalog().size() : pages().size()) + 4) / 5; scrollRows = Math.clamp(scrollRows - (int) Math.signum(scrollY), 0, Math.max(0, rows - 3)); return true; } return super.mouseScrolled(mouseX, mouseY, scrollX, scrollY); } + + @Override public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (search != null && search.isFocused()) { + if (keyCode == 256) { + setFocused(null); + search.setFocused(false); + } else { + search.keyPressed(keyCode, scanCode, modifiers); + } + return true; + } + return super.keyPressed(keyCode, scanCode, modifiers); + } } diff --git a/src/main/java/net/dnorberg/rivencraft/gametest/WritingDataGameTests.java b/src/main/java/net/dnorberg/rivencraft/gametest/WritingDataGameTests.java index bcff5db..efa7958 100644 --- a/src/main/java/net/dnorberg/rivencraft/gametest/WritingDataGameTests.java +++ b/src/main/java/net/dnorberg/rivencraft/gametest/WritingDataGameTests.java @@ -244,14 +244,14 @@ public final class WritingDataGameTests { @GameTest(template = "empty") public static void linkPanelPreparationConsumesOnePageAndConfiguredInkOnlyOnSuccess(GameTestHelper helper) { var player = helper.makeMockServerPlayerInLevel(); - ItemStack pages = new ItemStack(ModItems.PAGE.get(), 2); + ItemStack pages = new ItemStack(Items.PAPER, 2); ItemStack vial = new ItemStack(ModItems.INK_VIAL.get()); player.setItemInHand(InteractionHand.MAIN_HAND, vial); player.setItemInHand(InteractionHand.OFF_HAND, pages); helper.assertTrue(WritingInkService.prepareLinkPanel(player, InteractionHand.MAIN_HAND), - "A blank Page and sufficient ink must prepare one link panel"); - helper.assertValueEqual(pages.getCount(), 1, "Preparation must consume exactly one blank Page"); + "Paper and sufficient ink must prepare one link panel"); + helper.assertValueEqual(pages.getCount(), 1, "Preparation must consume exactly one paper"); helper.assertValueEqual(InkVialItem.remaining(player.getMainHandItem()), InkVialItem.CAPACITY - WritingInkService.linkPanelCost(), "Preparation must consume exactly the server-configured ink cost"); @@ -274,14 +274,14 @@ public final class WritingDataGameTests { ItemStack lowInk = new ItemStack(ModItems.INK_VIAL.get()); lowInk.set(ModDataComponents.INK_REMAINING.get(), WritingInkService.linkPanelCost() - 1); player.setItemInHand(InteractionHand.MAIN_HAND, lowInk); - player.setItemInHand(InteractionHand.OFF_HAND, new ItemStack(ModItems.PAGE.get())); + player.setItemInHand(InteractionHand.OFF_HAND, new ItemStack(Items.PAPER)); helper.assertFalse(WritingInkService.prepareLinkPanel(player, InteractionHand.MAIN_HAND), "Insufficient ink must reject link-panel preparation"); helper.assertValueEqual(InkVialItem.remaining(player.getMainHandItem()), WritingInkService.linkPanelCost() - 1, "Insufficient ink must remain untouched"); - helper.assertValueEqual(PageItem.content(player.getOffhandItem()).kind(), PageContent.Kind.BLANK, - "Insufficient ink must leave the Page blank"); + helper.assertTrue(player.getOffhandItem().is(Items.PAPER), + "Insufficient ink must leave paper untouched"); lowInk.set(ModDataComponents.INK_REMAINING.get(), WritingInkService.linkPanelCost()); helper.assertTrue(WritingInkService.prepareLinkPanel(player, InteractionHand.MAIN_HAND), @@ -290,7 +290,7 @@ public final class WritingDataGameTests { "A depleted Ink Vial must return a reusable glass bottle"); helper.assertValueEqual(PageItem.content(player.getOffhandItem()).kind(), PageContent.Kind.LINK_PANEL, - "A single blank Page must be replaced by one Link Panel Page"); + "A single paper must be replaced by one Link Panel Page"); helper.succeed(); } @@ -298,7 +298,7 @@ public final class WritingDataGameTests { public static void stackedInkVialsSplitWithoutChangingTheOtherVials(GameTestHelper helper) { var player = helper.makeMockServerPlayerInLevel(); player.setItemInHand(InteractionHand.MAIN_HAND, new ItemStack(ModItems.INK_VIAL.get(), 2)); - player.setItemInHand(InteractionHand.OFF_HAND, new ItemStack(ModItems.PAGE.get())); + player.setItemInHand(InteractionHand.OFF_HAND, new ItemStack(Items.PAPER)); helper.assertTrue(WritingInkService.prepareLinkPanel(player, InteractionHand.MAIN_HAND), "A stack of full vials must support preparing one panel"); diff --git a/src/main/java/net/dnorberg/rivencraft/gametest/WritingDeskGameTests.java b/src/main/java/net/dnorberg/rivencraft/gametest/WritingDeskGameTests.java index 02e26cc..88bfd30 100644 --- a/src/main/java/net/dnorberg/rivencraft/gametest/WritingDeskGameTests.java +++ b/src/main/java/net/dnorberg/rivencraft/gametest/WritingDeskGameTests.java @@ -9,7 +9,10 @@ import net.dnorberg.rivencraft.registry.ModBlocks; import net.dnorberg.rivencraft.registry.ModDataComponents; import net.dnorberg.rivencraft.registry.ModItems; import net.dnorberg.rivencraft.writing.NotebookContents; +import net.dnorberg.rivencraft.writing.SymbolReference; +import net.dnorberg.rivencraft.writing.WritingDeskCatalog; import net.minecraft.core.BlockPos; +import net.minecraft.resources.ResourceLocation; import net.minecraft.gametest.framework.GameTest; import net.minecraft.gametest.framework.GameTestHelper; import net.minecraft.world.entity.item.ItemEntity; @@ -32,7 +35,7 @@ public final class WritingDeskGameTests { var original = new WritingDeskBlockEntity(pos, ModBlocks.WRITING_DESK.get().defaultBlockState()); original.setItem(0, new ItemStack(ModItems.NOTEBOOK.get())); original.setItem(WritingDeskBlockEntity.INK, new ItemStack(ModItems.INK_VIAL.get())); - original.setItem(WritingDeskBlockEntity.PAPER, new ItemStack(ModItems.PAGE.get(), 12)); + original.setItem(WritingDeskBlockEntity.PAPER, new ItemStack(Items.PAPER, 12)); original.setActiveCollection(2); var saved = original.saveWithoutMetadata(helper.getLevel().registryAccess()); var restored = new WritingDeskBlockEntity(pos, ModBlocks.WRITING_DESK.get().defaultBlockState()); @@ -52,12 +55,12 @@ public final class WritingDeskGameTests { WritingDeskBlockEntity desk = (WritingDeskBlockEntity) helper.getLevel().getBlockEntity(pos); desk.setItem(0, new ItemStack(ModItems.NOTEBOOK.get())); desk.setItem(WritingDeskBlockEntity.INK, new ItemStack(ModItems.INK_VIAL.get())); - desk.setItem(WritingDeskBlockEntity.PAPER, new ItemStack(ModItems.PAGE.get(), 12)); + desk.setItem(WritingDeskBlockEntity.PAPER, new ItemStack(Items.PAPER, 12)); helper.getLevel().setBlock(pos, Blocks.AIR.defaultBlockState(), 3); var drops = helper.getLevel().getEntitiesOfClass(ItemEntity.class, new AABB(pos).inflate(2)); int notebooks = drops.stream().filter(e -> e.getItem().is(ModItems.NOTEBOOK.get())).mapToInt(e -> e.getItem().getCount()).sum(); int ink = drops.stream().filter(e -> e.getItem().is(ModItems.INK_VIAL.get())).mapToInt(e -> e.getItem().getCount()).sum(); - int pages = drops.stream().filter(e -> e.getItem().is(ModItems.PAGE.get())).mapToInt(e -> e.getItem().getCount()).sum(); + int pages = drops.stream().filter(e -> e.getItem().is(Items.PAPER)).mapToInt(e -> e.getItem().getCount()).sum(); helper.assertValueEqual(notebooks, 1, "Notebook must drop exactly once"); helper.assertValueEqual(ink, 1, "Ink must drop exactly once"); helper.assertValueEqual(pages, 12, "Pages must drop without loss"); @@ -206,4 +209,60 @@ public final class WritingDeskGameTests { "Shared desk must spend paper only once"); helper.succeed(); } + + @GameTest(template = "empty") + public static void catalogAggregatesKnownSymbolsAcrossCollections(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); + ResourceLocation known = ResourceLocation.parse("rivencraft:terrain/standard"); + ItemStack page = PageItem.createSymbolPage(new SymbolReference(known)); + ItemStack notebook = new ItemStack(ModItems.NOTEBOOK.get()); + notebook.set(ModDataComponents.NOTEBOOK_CONTENTS.get(), new NotebookContents(List.of(page.copy()))); + desk.setItem(0, page); + desk.setItem(1, notebook); + helper.assertValueEqual(WritingDeskCatalog.available(desk).size(), 1, + "Duplicate symbols across loose pages and notebooks must appear once"); + helper.assertValueEqual(WritingDeskCatalog.available(desk).getFirst().id(), known, + "Catalog must retain the authoritative symbol ID"); + 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, 101), "Out-of-range catalog index must be rejected"); + helper.assertTrue(menu.clickMenuButton(player, 100), "Known catalog symbol must be selectable"); + desk.setItem(WritingDeskBlockEntity.PAPER, new ItemStack(Items.PAPER)); + desk.setItem(WritingDeskBlockEntity.INK, new ItemStack(ModItems.INK_VIAL.get())); + menu.broadcastChanges(); + helper.assertTrue(menu.getSlot(WritingDeskMenu.COPY_RESULT).getItem().is(ModItems.PAGE.get()), + "Catalog selection must preview a copy of the known symbol"); + desk.setItem(0, ItemStack.EMPTY); + desk.setItem(1, ItemStack.EMPTY); + menu.broadcastChanges(); + helper.assertTrue(menu.catalog().isEmpty(), "Removed sources must invalidate catalog for the viewer"); + helper.assertTrue(menu.getSlot(WritingDeskMenu.COPY_RESULT).getItem().isEmpty(), + "Removed sources must invalidate the copy output"); + helper.succeed(); + } + + @GameTest(template = "empty") + public static void fullInventoryCannotConsumeCopyMaterials(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); + for (int i = 0; i < 36; i++) player.getInventory().setItem(i, new ItemStack(Items.COBBLESTONE, 64)); + WritingDeskMenu menu = new WritingDeskMenu(1, player.getInventory(), desk); + helper.assertTrue(menu.quickMoveStack(player, WritingDeskMenu.COPY_RESULT).isEmpty(), + "Full inventory must reject shift-click copy"); + helper.assertValueEqual(desk.getItem(WritingDeskBlockEntity.PAPER).getCount(), 1, + "Rejected copy must preserve paper"); + helper.assertValueEqual(desk.inkRemaining(), 0, "Rejected copy must preserve ink meter"); + helper.assertTrue(desk.getItem(WritingDeskBlockEntity.INK).is(ModItems.INK_VIAL.get()), + "Rejected copy must preserve ink vial"); + helper.succeed(); + } } diff --git a/src/main/java/net/dnorberg/rivencraft/menu/WritingDeskMenu.java b/src/main/java/net/dnorberg/rivencraft/menu/WritingDeskMenu.java index fab1c42..172b6f4 100644 --- a/src/main/java/net/dnorberg/rivencraft/menu/WritingDeskMenu.java +++ b/src/main/java/net/dnorberg/rivencraft/menu/WritingDeskMenu.java @@ -7,6 +7,14 @@ import net.dnorberg.rivencraft.registry.ModItems; import net.dnorberg.rivencraft.registry.ModMenus; import net.dnorberg.rivencraft.writing.NotebookContents; import net.dnorberg.rivencraft.writing.PageContent; +import net.dnorberg.rivencraft.writing.SymbolCatalog; +import net.dnorberg.rivencraft.writing.SymbolDisplayMetadata; +import net.dnorberg.rivencraft.writing.SymbolReference; +import net.dnorberg.rivencraft.writing.WritingDeskCatalog; +import net.dnorberg.rivencraft.network.WritingDeskCatalogPayload; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.neoforged.neoforge.network.PacketDistributor; import java.util.List; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.world.Container; @@ -29,10 +37,14 @@ public final class WritingDeskMenu extends AbstractContainerMenu { private int syncedActiveCollection; private int syncedInkRemaining; private int selectedPage; + private ResourceLocation selectedSymbol; + private List catalog = List.of(); + private final ServerPlayer serverViewer; public WritingDeskMenu(int id, Inventory inventory, RegistryFriendlyByteBuf data) { this(id, inventory, inventory.player.level().getBlockEntity(data.readBlockPos()) instanceof WritingDeskBlockEntity found ? found : null); + catalog = WritingDeskCatalog.read(data); } public WritingDeskMenu(int id, Inventory inventory, WritingDeskBlockEntity blockEntity) { @@ -40,6 +52,8 @@ public final class WritingDeskMenu extends AbstractContainerMenu { this.blockEntity = blockEntity; this.desk = blockEntity == null ? new SimpleContainer(WritingDeskBlockEntity.SIZE) : blockEntity; this.clientSide = inventory.player.level().isClientSide; + this.serverViewer = inventory.player instanceof ServerPlayer serverPlayer ? serverPlayer : null; + if (blockEntity != null && !clientSide) catalog = WritingDeskCatalog.available(blockEntity); addDataSlot(new DataSlot() { @Override public int get() { return WritingDeskMenu.this.blockEntity == null ? syncedActiveCollection : WritingDeskMenu.this.blockEntity.activeCollection(); } @@ -70,14 +84,28 @@ public final class WritingDeskMenu extends AbstractContainerMenu { public int activeCollection() { return syncedActiveCollection; } public int inkRemaining() { return syncedInkRemaining; } + public List catalog() { return catalog; } + public void setCatalog(List symbols) { catalog = List.copyOf(symbols); } @Override public boolean clickMenuButton(Player player, int id) { if (blockEntity == null || !stillValid(player)) return false; if (id >= 0 && id < WritingDeskBlockEntity.COLLECTIONS) { blockEntity.setActiveCollection(id); selectedPage = 0; + selectedSymbol = null; } else if (id >= 16 && id < 16 + NotebookContents.MAX_PAGES) { selectedPage = id - 16; + selectedSymbol = null; + } else if (id >= 100 && id < 100 + catalog.size()) { + if (!catalog.equals(WritingDeskCatalog.available(blockEntity))) { + refreshCatalog(); + return false; + } + ResourceLocation requested = catalog.get(id - 100).id(); + if (!WritingDeskCatalog.available(blockEntity).stream().anyMatch(entry -> entry.id().equals(requested))) { + return false; + } + selectedSymbol = requested; } else return false; refreshOutput(); return true; @@ -118,8 +146,7 @@ public final class WritingDeskMenu extends AbstractContainerMenu { private boolean moveIntoDesk(ItemStack moving) { if (moving.is(ModItems.INK_VIAL.get())) return moveItemStackTo(moving, 4, 5, false); if (moving.is(ModItems.NOTEBOOK.get())) return moveItemStackTo(moving, 0, 4, false); - if (moving.is(Items.PAPER) || moving.is(ModItems.PAGE.get()) - && PageItem.content(moving).kind() == PageContent.Kind.BLANK) + if (moving.is(Items.PAPER)) return moveItemStackTo(moving, WritingDeskBlockEntity.PAPER, WritingDeskBlockEntity.PAPER + 1, false); if (moving.is(ModItems.PAGE.get())) return moveItemStackTo(moving, 0, 4, false); @@ -127,6 +154,14 @@ public final class WritingDeskMenu extends AbstractContainerMenu { } private ItemStack selectedSource() { + if (selectedSymbol != null) { + SymbolReference reference = new SymbolReference(selectedSymbol); + return SymbolCatalog.isKnown(reference) + && blockEntity != null + && WritingDeskCatalog.available(blockEntity).stream() + .anyMatch(entry -> entry.id().equals(selectedSymbol)) + ? PageItem.createSymbolPage(reference) : ItemStack.EMPTY; + } ItemStack collection = desk.getItem(blockEntity == null ? syncedActiveCollection : blockEntity.activeCollection()); if (collection.is(ModItems.PAGE.get())) return selectedPage == 0 ? collection : ItemStack.EMPTY; if (collection.is(ModItems.NOTEBOOK.get())) { @@ -150,7 +185,19 @@ public final class WritingDeskMenu extends AbstractContainerMenu { if (!ItemStack.matches(result.getItem(0), preview)) result.setItem(0, preview); } + private void refreshCatalog() { + if (clientSide || blockEntity == null) return; + List current = WritingDeskCatalog.available(blockEntity); + if (!current.equals(catalog)) { + catalog = current; + if (serverViewer != null && serverViewer.containerMenu == this) { + PacketDistributor.sendToPlayer(serverViewer, new WritingDeskCatalogPayload(containerId, catalog)); + } + } + } + @Override public void broadcastChanges() { + refreshCatalog(); refreshOutput(); super.broadcastChanges(); } @@ -161,6 +208,7 @@ public final class WritingDeskMenu extends AbstractContainerMenu { player.closeContainer(); return; } + refreshCatalog(); refreshOutput(); super.clicked(slotId, button, clickType, player); } diff --git a/src/main/java/net/dnorberg/rivencraft/network/ModNetworking.java b/src/main/java/net/dnorberg/rivencraft/network/ModNetworking.java index 3ea38b5..cbad872 100644 --- a/src/main/java/net/dnorberg/rivencraft/network/ModNetworking.java +++ b/src/main/java/net/dnorberg/rivencraft/network/ModNetworking.java @@ -9,6 +9,7 @@ import net.dnorberg.rivencraft.block.entity.BookStandBlockEntity; import net.dnorberg.rivencraft.client.ClientLinkPreviewHandler; import net.dnorberg.rivencraft.client.ClientBookInteractionHandler; import net.dnorberg.rivencraft.client.ClientAgeCelestialAngles; +import net.dnorberg.rivencraft.client.ClientWritingDeskCatalog; import net.dnorberg.rivencraft.event.AgeCelestialAngleSync; import net.dnorberg.rivencraft.item.DescriptiveBookItem; import net.dnorberg.rivencraft.item.LinkingBookItem; @@ -68,6 +69,8 @@ public final class ModNetworking { ClientLinkPreviewHandler::handle); registrar.playToClient(AgeCelestialAnglePayload.TYPE, AgeCelestialAnglePayload.STREAM_CODEC, ClientAgeCelestialAngles::handle); + registrar.playToClient(WritingDeskCatalogPayload.TYPE, WritingDeskCatalogPayload.STREAM_CODEC, + ClientWritingDeskCatalog::handle); } private static void requestDescriptiveBookScreen(RequestDescriptiveBookScreenPayload payload, IPayloadContext context) { diff --git a/src/main/java/net/dnorberg/rivencraft/network/WritingDeskCatalogPayload.java b/src/main/java/net/dnorberg/rivencraft/network/WritingDeskCatalogPayload.java new file mode 100644 index 0000000..d50458b --- /dev/null +++ b/src/main/java/net/dnorberg/rivencraft/network/WritingDeskCatalogPayload.java @@ -0,0 +1,26 @@ +package net.dnorberg.rivencraft.network; + +import java.util.List; +import net.dnorberg.rivencraft.Rivencraft; +import net.dnorberg.rivencraft.writing.SymbolDisplayMetadata; +import net.dnorberg.rivencraft.writing.WritingDeskCatalog; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; + +/** Current server-approved symbols from this open Writing Desk menu. */ +public record WritingDeskCatalogPayload(int containerId, List symbols) + implements CustomPacketPayload { + public static final Type TYPE = new Type<>( + ResourceLocation.fromNamespaceAndPath(Rivencraft.MOD_ID, "writing_desk_catalog")); + public static final StreamCodec STREAM_CODEC = + StreamCodec.of( + (buffer, payload) -> { + buffer.writeVarInt(payload.containerId()); + WritingDeskCatalog.write(buffer, payload.symbols()); + }, + buffer -> new WritingDeskCatalogPayload(buffer.readVarInt(), WritingDeskCatalog.read(buffer))); + + @Override public Type type() { return TYPE; } +} diff --git a/src/main/java/net/dnorberg/rivencraft/registry/ModCreativeTabs.java b/src/main/java/net/dnorberg/rivencraft/registry/ModCreativeTabs.java index 9a1683d..2f5891c 100644 --- a/src/main/java/net/dnorberg/rivencraft/registry/ModCreativeTabs.java +++ b/src/main/java/net/dnorberg/rivencraft/registry/ModCreativeTabs.java @@ -31,7 +31,6 @@ public final class ModCreativeTabs { .title(Component.translatable("itemGroup.rivencraft.pages")) .icon(PageItem::createLinkPanelPage) .displayItems((parameters, output) -> { - output.accept(ModItems.PAGE.get()); output.accept(PageItem.createLinkPanelPage()); }) .build()); diff --git a/src/main/java/net/dnorberg/rivencraft/writing/WritingDeskCatalog.java b/src/main/java/net/dnorberg/rivencraft/writing/WritingDeskCatalog.java new file mode 100644 index 0000000..dab7d77 --- /dev/null +++ b/src/main/java/net/dnorberg/rivencraft/writing/WritingDeskCatalog.java @@ -0,0 +1,63 @@ +package net.dnorberg.rivencraft.writing; + +import java.util.ArrayList; +import java.util.Comparator; +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; +import net.dnorberg.rivencraft.registry.ModItems; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; + +/** A bounded, server-derived view of symbols physically present in the desk. */ +public final class WritingDeskCatalog { + private static final int MAX_ENTRIES = WritingDeskBlockEntity.COLLECTIONS * NotebookContents.MAX_PAGES; + + private WritingDeskCatalog() {} + + public static List available(WritingDeskBlockEntity desk) { + Map unique = new TreeMap<>(); + for (int slot = 0; slot < WritingDeskBlockEntity.COLLECTIONS; slot++) { + ItemStack collection = desk.getItem(slot); + if (collection.is(ModItems.PAGE.get())) add(unique, collection); + if (collection.is(ModItems.NOTEBOOK.get())) { + List pages = collection.getOrDefault( + ModDataComponents.NOTEBOOK_CONTENTS.get(), NotebookContents.EMPTY).pages(); + pages.forEach(page -> add(unique, page)); + } + } + return unique.values().stream() + .sorted(Comparator.comparing((SymbolDisplayMetadata symbol) -> symbol.category().toString()) + .thenComparing(symbol -> symbol.id().toString())) + .toList(); + } + + private static void add(Map unique, ItemStack page) { + if (!page.is(ModItems.PAGE.get())) return; + PageContent content = PageItem.content(page).resolve(); + if (content.kind() != PageContent.Kind.SYMBOL) return; + SymbolReference reference = content.symbol().orElseThrow(); + SymbolCatalog.find(reference).ifPresent(definition -> + unique.put(reference.id(), definition.display(reference.id()))); + } + + public static void write(RegistryFriendlyByteBuf buffer, List entries) { + if (entries.size() > MAX_ENTRIES) throw new IllegalArgumentException("Desk symbol catalog exceeds limit"); + buffer.writeVarInt(entries.size()); + entries.forEach(entry -> SymbolDisplayMetadata.STREAM_CODEC.encode(buffer, entry)); + } + + public static List read(RegistryFriendlyByteBuf buffer) { + int count = buffer.readVarInt(); + if (count < 0 || count > MAX_ENTRIES) throw new IllegalArgumentException("Invalid desk symbol count " + count); + List entries = new ArrayList<>(count); + for (int index = 0; index < count; index++) { + entries.add(SymbolDisplayMetadata.STREAM_CODEC.decode(buffer)); + } + return List.copyOf(entries); + } +} diff --git a/src/main/java/net/dnorberg/rivencraft/writing/WritingInkService.java b/src/main/java/net/dnorberg/rivencraft/writing/WritingInkService.java index e81a6ed..6aaa4fb 100644 --- a/src/main/java/net/dnorberg/rivencraft/writing/WritingInkService.java +++ b/src/main/java/net/dnorberg/rivencraft/writing/WritingInkService.java @@ -29,7 +29,7 @@ public final class WritingInkService { int remaining = InkVialItem.remaining(vial); return vial.is(ModItems.INK_VIAL.get()) && remaining >= linkPanelCost() && remaining <= InkVialItem.CAPACITY - && page.is(ModItems.PAGE.get()) && PageItem.content(page).kind() == PageContent.Kind.BLANK; + && page.is(Items.PAPER); } public static boolean prepareLinkPanel(Player player, InteractionHand vialHand) { diff --git a/src/main/resources/assets/rivencraft/lang/en_us.json b/src/main/resources/assets/rivencraft/lang/en_us.json index ccaae3f..7fab302 100644 --- a/src/main/resources/assets/rivencraft/lang/en_us.json +++ b/src/main/resources/assets/rivencraft/lang/en_us.json @@ -19,8 +19,10 @@ "block.rivencraft.writing_desk": "Writing Desk", "container.rivencraft.writing_desk": "Writing Desk", "screen.rivencraft.writing_desk.ink": "Ink: %s/%s", + "screen.rivencraft.writing_desk.search": "Search", + "screen.rivencraft.writing_desk.no_symbols": "No available symbols", "item.rivencraft.ink_vial.remaining": "Ink: %s/%s", - "item.rivencraft.ink_vial.usage": "Use with a blank Page in the other hand to prepare a Link Panel.", + "item.rivencraft.ink_vial.usage": "Use with paper in the other hand to prepare a Link Panel.", "screen.rivencraft.notebook.search": "Search pages", "screen.rivencraft.notebook.move_previous": "Move selected page left", "screen.rivencraft.notebook.move_next": "Move selected page right", diff --git a/src/main/resources/assets/rivencraft/models/block/writing_desk.json b/src/main/resources/assets/rivencraft/models/block/writing_desk.json index 05530a2..84ad793 100644 --- a/src/main/resources/assets/rivencraft/models/block/writing_desk.json +++ b/src/main/resources/assets/rivencraft/models/block/writing_desk.json @@ -1,8 +1,69 @@ { - "parent": "minecraft:block/cube_bottom_top", + "parent": "minecraft:block/block", + "ambientocclusion": false, "textures": { - "top": "rivencraft:item/writing_desk", - "side": "minecraft:block/oak_planks", - "bottom": "minecraft:block/oak_planks" - } + "particle": "rivencraft:block/writing_desk", + "desk": "rivencraft:block/writing_desk" + }, + "elements": [ + { + "from": [0, 8, 0], "to": [16, 9, 16], + "faces": { + "up": {"uv": [1, 0, 3, 2], "texture": "#desk"}, + "down": {"uv": [1, 2, 3, 4], "texture": "#desk"}, + "north": {"uv": [1, 0, 3, 0.125], "texture": "#desk"}, + "south": {"uv": [1, 0, 3, 0.125], "texture": "#desk"}, + "east": {"uv": [1, 0, 3, 0.125], "texture": "#desk"}, + "west": {"uv": [1, 0, 3, 0.125], "texture": "#desk"} + } + }, + { + "from": [0, 0, 1], "to": [2, 8, 15], + "faces": { + "north": {"uv": [5.625, 0.125, 5.875, 2], "texture": "#desk"}, + "south": {"uv": [5.625, 0.125, 5.875, 2], "texture": "#desk"}, + "east": {"uv": [5.625, 0.125, 5.875, 2], "texture": "#desk"}, + "west": {"uv": [5.625, 0.125, 5.875, 2], "texture": "#desk"} + } + }, + { + "from": [14, 0, 1], "to": [16, 8, 15], + "faces": { + "north": {"uv": [5.625, 0.125, 5.875, 2], "texture": "#desk"}, + "south": {"uv": [5.625, 0.125, 5.875, 2], "texture": "#desk"}, + "east": {"uv": [5.625, 0.125, 5.875, 2], "texture": "#desk"}, + "west": {"uv": [5.625, 0.125, 5.875, 2], "texture": "#desk"} + } + }, + { + "from": [1, 3, 2], "to": [15, 4, 14], + "faces": { + "up": {"uv": [0, 4.25, 3.75, 6.125], "texture": "#desk"}, + "down": {"uv": [0, 4.25, 3.75, 6.125], "texture": "#desk"}, + "north": {"uv": [0, 4.25, 3.75, 4.5], "texture": "#desk"}, + "south": {"uv": [0, 4.25, 3.75, 4.5], "texture": "#desk"}, + "east": {"uv": [0, 4.25, 3.75, 4.5], "texture": "#desk"}, + "west": {"uv": [0, 4.25, 3.75, 4.5], "texture": "#desk"} + } + }, + { + "from": [1, 9, 14], "to": [15, 15, 16], + "faces": { + "north": {"uv": [8, 0, 11.75, 2], "texture": "#desk"}, + "south": {"uv": [8, 0, 11.75, 2], "texture": "#desk"}, + "east": {"uv": [8, 0, 8.25, 2], "texture": "#desk"}, + "west": {"uv": [8, 0, 8.25, 2], "texture": "#desk"}, + "up": {"uv": [8, 0, 11.75, 0.25], "texture": "#desk"} + } + }, + { + "from": [1, 12, 9], "to": [15, 13, 15], + "faces": { + "up": {"uv": [8, 3.625, 11.75, 4.375], "texture": "#desk"}, + "down": {"uv": [8, 3.625, 11.75, 4.375], "texture": "#desk"}, + "north": {"uv": [8, 3.625, 11.75, 3.75], "texture": "#desk"}, + "south": {"uv": [8, 3.625, 11.75, 3.75], "texture": "#desk"} + } + } + ] } diff --git a/src/main/resources/assets/rivencraft/models/item/writing_desk.json b/src/main/resources/assets/rivencraft/models/item/writing_desk.json index 9104ac8..7d99ef8 100644 --- a/src/main/resources/assets/rivencraft/models/item/writing_desk.json +++ b/src/main/resources/assets/rivencraft/models/item/writing_desk.json @@ -1,4 +1,3 @@ { - "parent": "minecraft:item/generated", - "textures": { "layer0": "rivencraft:item/writing_desk" } + "parent": "rivencraft:block/writing_desk" } diff --git a/src/main/resources/assets/rivencraft/textures/block/writing_desk.png b/src/main/resources/assets/rivencraft/textures/block/writing_desk.png new file mode 100644 index 0000000..60bdfd9 Binary files /dev/null and b/src/main/resources/assets/rivencraft/textures/block/writing_desk.png differ diff --git a/src/main/resources/data/rivencraft/recipe/page.json b/src/main/resources/data/rivencraft/recipe/page.json deleted file mode 100644 index b90ef76..0000000 --- a/src/main/resources/data/rivencraft/recipe/page.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "type": "minecraft:crafting_shapeless", - "category": "misc", - "ingredients": [ - { - "item": "minecraft:paper" - } - ], - "result": { - "count": 1, - "id": "rivencraft:page" - } -}