From be8440ddb548d41db345611fb721d8389e888f02 Mon Sep 17 00:00:00 2001 From: Dustin Norberg Date: Thu, 5 Nov 2026 22:04:24 -0500 Subject: [PATCH] Port collections, dropped books, return links and Writing Desk to 26.3 --- README.md | 2 +- dev images/Pasted image.png | Bin 165953 -> 0 bytes dev images/current 26.3.png | Bin 0 -> 74082 bytes docs/MINECRAFT_26_3_PORT.md | 60 +++ ports/mc263/README.md | 239 +++++++- ports/mc263/RELEASE_NOTES_1.0.1.106-beta.1.md | 85 +++ ports/mc263/build.gradle | 21 + ports/mc263/gradle.properties | 2 +- .../net/dnorberg/rivencraft/PortConfig.java | 14 +- .../net/dnorberg/rivencraft/Rivencraft.java | 37 ++ .../rivencraft/age/PortAgeCommands.java | 190 +++++++ .../rivencraft/age/PortAgeTravel.java | 4 +- .../rivencraft/age/PortAgeVisits.java | 27 + .../age/PortBinderCollectionProbe.java | 99 ++++ .../age/PortCollectionExtrasProbe.java | 109 ++++ .../rivencraft/age/PortDroppedBookProbe.java | 81 +++ .../rivencraft/age/PortNotebookProbe.java | 94 ++++ .../rivencraft/age/PortReturnLinks.java | 44 ++ .../rivencraft/age/PortWritingDeskProbe.java | 98 ++++ .../rivencraft/block/CloudPlatformBlock.java | 17 + .../rivencraft/block/WritingDeskBlock.java | 83 +++ .../entity/PortBookBinderBlockEntity.java | 30 ++ .../block/entity/WritingDeskBlockEntity.java | 173 ++++++ .../rivencraft/client/AgeCatalogClient.java | 1 + .../client/ClientWritingDeskCatalog.java | 20 + .../client/DroppedBookRenderer.java | 57 ++ .../rivencraft/client/NotebookScreen.java | 317 +++++++++++ .../client/PortBookBinderClientEvents.java | 4 + .../client/WritingDeskRenderer.java | 60 +++ .../rivencraft/client/WritingDeskScreen.java | 510 ++++++++++++++++++ .../rivencraft/entity/DroppedBookEntity.java | 51 ++ .../rivencraft/item/CollationFolderItem.java | 25 + .../rivencraft/item/NotebookItem.java | 44 ++ .../dnorberg/rivencraft/item/PageItem.java | 16 + .../rivencraft/item/PortHandheldTravel.java | 35 ++ .../rivencraft/item/SealedNotebookItem.java | 43 ++ .../rivencraft/item/SymbolPortfolioItem.java | 39 ++ .../rivencraft/menu/NotebookMenu.java | 205 +++++++ .../rivencraft/menu/PortBookBinderMenu.java | 14 + .../rivencraft/menu/WritingDeskMenu.java | 463 ++++++++++++++++ .../network/PortBookNetworking.java | 18 +- .../network/WritingDeskCatalogPayload.java | 26 + .../rivencraft/registry/ModCreativeTabs.java | 14 + .../rivencraft/writing/NotebookContents.java | 90 ++++ .../rivencraft/writing/PageContent.java | 95 ++++ .../rivencraft/writing/PortRegistryPages.java | 1 + .../writing/PortSymbolNumerals.java | 11 + .../rivencraft/writing/PortSymbolPages.java | 9 + .../writing/SymbolDisplayMetadata.java | 48 ++ .../rivencraft/writing/SymbolReference.java | 24 + .../writing/WritingDeskCatalog.java | 64 +++ .../rivencraft/items/collation_folder.json | 1 + .../assets/rivencraft/items/notebook.json | 1 + .../rivencraft/items/sealed_notebook.json | 1 + .../rivencraft/items/symbol_portfolio.json | 1 + .../assets/rivencraft/items/writing_desk.json | 1 + .../advancement/recipes/misc/notebook.json | 9 + .../recipes/misc/writing_desk.json | 9 + .../data/rivencraft/recipe/notebook.json | 7 + .../data/rivencraft/recipe/writing_desk.json | 6 + 60 files changed, 3836 insertions(+), 13 deletions(-) delete mode 100644 dev images/Pasted image.png create mode 100644 dev images/current 26.3.png create mode 100644 ports/mc263/RELEASE_NOTES_1.0.1.106-beta.1.md create mode 100644 ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortAgeVisits.java create mode 100644 ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortBinderCollectionProbe.java create mode 100644 ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortCollectionExtrasProbe.java create mode 100644 ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortDroppedBookProbe.java create mode 100644 ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortNotebookProbe.java create mode 100644 ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortReturnLinks.java create mode 100644 ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortWritingDeskProbe.java create mode 100644 ports/mc263/src/main/java/net/dnorberg/rivencraft/block/CloudPlatformBlock.java create mode 100644 ports/mc263/src/main/java/net/dnorberg/rivencraft/block/WritingDeskBlock.java create mode 100644 ports/mc263/src/main/java/net/dnorberg/rivencraft/block/entity/WritingDeskBlockEntity.java create mode 100644 ports/mc263/src/main/java/net/dnorberg/rivencraft/client/ClientWritingDeskCatalog.java create mode 100644 ports/mc263/src/main/java/net/dnorberg/rivencraft/client/DroppedBookRenderer.java create mode 100644 ports/mc263/src/main/java/net/dnorberg/rivencraft/client/NotebookScreen.java create mode 100644 ports/mc263/src/main/java/net/dnorberg/rivencraft/client/WritingDeskRenderer.java create mode 100644 ports/mc263/src/main/java/net/dnorberg/rivencraft/client/WritingDeskScreen.java create mode 100644 ports/mc263/src/main/java/net/dnorberg/rivencraft/entity/DroppedBookEntity.java create mode 100644 ports/mc263/src/main/java/net/dnorberg/rivencraft/item/CollationFolderItem.java create mode 100644 ports/mc263/src/main/java/net/dnorberg/rivencraft/item/NotebookItem.java create mode 100644 ports/mc263/src/main/java/net/dnorberg/rivencraft/item/PortHandheldTravel.java create mode 100644 ports/mc263/src/main/java/net/dnorberg/rivencraft/item/SealedNotebookItem.java create mode 100644 ports/mc263/src/main/java/net/dnorberg/rivencraft/item/SymbolPortfolioItem.java create mode 100644 ports/mc263/src/main/java/net/dnorberg/rivencraft/menu/NotebookMenu.java create mode 100644 ports/mc263/src/main/java/net/dnorberg/rivencraft/menu/WritingDeskMenu.java create mode 100644 ports/mc263/src/main/java/net/dnorberg/rivencraft/network/WritingDeskCatalogPayload.java create mode 100644 ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/NotebookContents.java create mode 100644 ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/PageContent.java create mode 100644 ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/SymbolDisplayMetadata.java create mode 100644 ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/SymbolReference.java create mode 100644 ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/WritingDeskCatalog.java create mode 100644 ports/mc263/src/main/resources/assets/rivencraft/items/collation_folder.json create mode 100644 ports/mc263/src/main/resources/assets/rivencraft/items/notebook.json create mode 100644 ports/mc263/src/main/resources/assets/rivencraft/items/sealed_notebook.json create mode 100644 ports/mc263/src/main/resources/assets/rivencraft/items/symbol_portfolio.json create mode 100644 ports/mc263/src/main/resources/assets/rivencraft/items/writing_desk.json create mode 100644 ports/mc263/src/main/resources/data/rivencraft/advancement/recipes/misc/notebook.json create mode 100644 ports/mc263/src/main/resources/data/rivencraft/advancement/recipes/misc/writing_desk.json create mode 100644 ports/mc263/src/main/resources/data/rivencraft/recipe/notebook.json create mode 100644 ports/mc263/src/main/resources/data/rivencraft/recipe/writing_desk.json diff --git a/README.md b/README.md index a9aba3a..0bbf48d 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ feature completion, and random structure selection. Nether/End noise-biome materials now follow their native rules. The beta does not yet provide full 1.21.1 gameplay or optional-integration parity. See the [26.3 beta guide](ports/mc263/README.md) and -[latest beta release notes](ports/mc263/RELEASE_NOTES_1.0.1.98-beta.1.md). +[latest beta release notes](ports/mc263/RELEASE_NOTES_1.0.1.106-beta.1.md). Keep the builds and their saves separate, and back up worlds before updating. ## Getting started diff --git a/dev images/Pasted image.png b/dev images/Pasted image.png deleted file mode 100644 index 6764c5a5715883c67c4868c61a8b383cc7747fee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 165953 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�-*NoRW@(iEtrLE4m_U- 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¨9WOgwQed=uqFCzmJL2T@6>1>W3gt53;yv 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

E`RcE44!N4YJw{_ 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 M07*qoM6N<$f;vMTK>z>% literal 0 HcmV?d00001 diff --git a/docs/MINECRAFT_26_3_PORT.md b/docs/MINECRAFT_26_3_PORT.md index 2c636a8..987b690 100644 --- a/docs/MINECRAFT_26_3_PORT.md +++ b/docs/MINECRAFT_26_3_PORT.md @@ -10,6 +10,66 @@ tracks issues #97–#103 under parent #96. ## Version boundary +Local slice `1.0.1.106-beta.1` ports the Writing Desk's two-part block, +88-tab inventory, authoritative catalog sync, copying transactions and 1.21.1 +GUI/model assets. Vial exchange reuses the existing shared helper; symbol copy +cost defaults to one. Modern Container removal handles content drops while +the block cleans up its partner. The renderer uses extracted item states. +Current input helpers also correct Notebook right-click/scrollbar behavior. +Page content is a derived view of the existing port components; no saved Page +schema is replaced. Progression awards and optional JEI integration remain +pending. Client appearance/input/catalog acceptance remains required. + +Local slice `1.0.1.105-beta.1` ports category Symbol Portfolios and Sealed +Notebooks, reusing item textures/models and collection menu/import behavior. +Portfolio volumes split at 252 pages. Sealed items use 1.21.1's 16-page draw +and candidate exclusions, with Survival consumption, Creative retention and +full-inventory drop fallback. No recipes are invented; discovery/library loot +and JEI integration remain later work. Unported effects are still marked red. + +Local slice `1.0.1.104-beta.1` ports 1.21.1's all-or-nothing collection import +to the current Binder menu: shift-click appends and cursor strip-click inserts +before a page. Occupied page order is preserved, gaps are compacted and the +empty collection is returned. Capacity/panel conflicts are checked before any +mutation. This connects the newly ported collections to actual book assembly; +the dedicated stored-collection input/UI remains later parity work. + +Local follow-up `1.0.1.103-beta.1` corrects old GLFW key literals retained in +the Notebook screen. Escape always reaches the normal container close path, +including focused search, and page-movement arrows use 26.3 key helpers. +The player reported the search-focused GUI could not be escaped in .102; +client keyboard acceptance must be repeated with .103. + +Local slice `1.0.1.102-beta.1` ports Notebook/Collation Folder storage, menu, +Creative entries, Notebook crafting and the existing sparse page editor. +The 1.21.1 capacities (252/27), Link Panel insertion rejection, held-item +locking and search keyboard handling are preserved. The GUI uses 26.3 input +events/extraction and the existing glyph-only item pipeline, while retaining +the original page atlas proportions. Discovery, sealed notebooks, portfolios, +Writing Desk and bulk Binder collection use remain unfinished. + +Local slice `1.0.1.101-beta.1` ports dropped-book entities, their open-book +renderer/textures, decay/weather damage and persistent components. Handheld +travel detaches only the active book and leaves it at departure after success; +failed travel or a cancelled entity spawn restores it. Shared handling covers +both book types and hands and keeps Return Link issuance after departure-book +handling. The newer render-state-based pickup path replaces the old copy() +workaround. Client rendering/pickup remain part of in-game validation. + +Local slice `1.0.1.100-beta.1` ports automatic Return Link policy from 1.21.1 +for handheld Descriptive Books, default DISABLED. ON_CREATION uses a persistent +world-wide per-Age marker; ALWAYS issues on each successful arrival. Exact +departure position/facing is captured before teleportation and issuance occurs +after Disarm, with inventory-full fallback to a destination item drop. Ordinary +Linking Books, failures and operator travel do not issue books. Stand/portal +integration remains pending with those systems. + +Local slice `1.0.1.99-beta.1` ports the dedicated non-collectible arrival +platform block from 1.21.1, reusing its opaque crystal model and strength 0.3. +Fallback landings use it instead of collectible Crystal Blocks. No item, +loot, pick-block result, or material Page is available. Existing platforms +and the port's landing-search/footprint policy are unchanged. + Local slice `1.0.1.98-beta.1` ports the 1.21.1 Accelerated environment page: stacking extra random block ticks in already-loaded player-nearby chunks, eight-chunk/16,384-attempt default budgets, fair rotating section allocation, diff --git a/ports/mc263/README.md b/ports/mc263/README.md index fcacc4b..e4e4eea 100644 --- a/ports/mc263/README.md +++ b/ports/mc263/README.md @@ -2,14 +2,15 @@ This is an **experimental beta port** for issues #97–#104, not gameplay parity with the Minecraft 1.21.1 release. The 26.3 backend persists separate Ages and -their seeds. Books, the Book Binder, and the Ink Mixer work, and the Binder can +their seeds. Books, the Book Binder, Ink Mixer, Writing Desk and page collections +are ported, and the Binder can author Standard, Amplified, Island, Flat, Void, or Cave terrain with supported biome-distribution pages, selected material/vegetation/structure features, and independent Age weather and lighting, multiple authored or seeded celestial bodies and eclipses, biome/material completion, and random features/structures. -Instability, crystal portals, discovery/stations, writing recovery and optional +Instability, crystal portals, remaining discovery/stations, writing recovery and optional integration parity remain unfinished. The latest published beta is -[1.0.1.98-beta.1](RELEASE_NOTES_1.0.1.98-beta.1.md). +[1.0.1.106-beta.1](RELEASE_NOTES_1.0.1.106-beta.1.md). 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. @@ -1413,6 +1414,238 @@ overflow/disabled budget arithmetic, unload/reload, reseed and full restart. No player was connected for the server checks, so live growth remains an in-game acceptance check. The test server was stopped and its world removed. +### Non-collectible arrival platforms + +The local `1.0.1.99-beta.1` slice ports 1.21.1's dedicated `cloud_platform` +block for fallback Age landings. It reuses the existing opaque Crystal Block +texture, has glass-like breaking strength (0.3), and produces no loot or +pick-block item. It has no item, recipe, Creative entry or generated material +Page. Ordinary Crystal Blocks retain their normal behavior. + +New fallback platforms use this block. Existing platforms made from ordinary +Crystal Blocks are not rewritten. This slice changes the fallback material; +the port's existing landing search and 3-by-3 fallback footprint remain. + +Test a new Void Age: arrive on the platform, check its appearance and footing, +then break a block in Survival and confirm there is no drop. Creative +pick-block must not supply an item. Compare a normal Crystal Block, and revisit +the platform after restart. Operator fixtures +`/rivencraft_port age create_platform ` and +`/rivencraft_port age platform_probe ` validate a disposable Void +platform's properties and saved blocks. +The build and disposable dedicated-server checks passed for actual Void +arrival generation, collision/opacity, strength, loot, pick-block exclusion, +material-page exclusion, unload/reload and full restart. Shared model assets +were verified in the JAR. The test server was stopped and its world removed; +client appearance and Survival breaking remain the in-game check. + +### Configurable automatic Return Links + +The local `1.0.1.100-beta.1` slice ports the 1.21.1 automatic Return Link +policy for successful handheld Descriptive Book arrivals. In +`config/rivencraft-local.toml`, set `[descriptive_books] return_link_mode` to +`DISABLED` (the default), `ON_CREATION`, or `ALWAYS`. ON_CREATION issues one +Return Link when the Age is first reached with that mode enabled, shared +across players in the world. ALWAYS issues one on each successful arrival. +Disabled/Always arrivals do not consume the ON_CREATION marker, matching +1.21.1. The marker persists across reload, reseeding and restart. + +Return Links retain the exact departure dimension, position, yaw and pitch. +They use ordinary Linking Book rules and have no automatic Link Panel +modifiers. They are issued after Disarm and dropped at the destination if +inventory space is unavailable. Failed travel, binding a book, ordinary +Linking Book travel and operator teleport probes do not issue Return Links. +Stands/portals remain pending and will need the same successful-travel hook. + +For the in-game check, confirm the default gives no Return Link. Then enable +ON_CREATION and visit a fresh Age twice: only the first arrival should grant +one. Use that book to return and check position/facing; restart and revisit +to confirm no duplicate. In ALWAYS, each arrival should grant a book. Test +a Disarm Descriptive Book and a full inventory as well. Restore DISABLED +after testing if automatic Return Links are unwanted. +The build and disposable-server fixtures passed mode decisions, book target +components, visit-record codec round trips, world persistence through +unload/reload, reseeding and restart, and config loading with DISABLED and +ON_CREATION. No player joined those fixtures; actual travel, Disarm and +inventory-full delivery remain the in-game acceptance check. The server was +stopped and its test world removed. + +### Handheld books left at departure + +The local `1.0.1.101-beta.1` slice ports the 1.21.1 dropped-book entity and +handheld travel ownership. Successful handheld Linking and Descriptive Book +travel removes the active book from its hand and leaves it at departure as +an open-book entity. The existing Linking/Descriptive entity textures and +skyward-facing model orientation are reused with 26.3's render-state API. +The other hand and ordinary inventory remain unchanged unless Disarm applies. + +Failed travel restores the book. A rejected dropped-entity spawn also restores +it instead of consuming it. Return Links are issued after the active book is +left behind. Dropped books have a one-second pickup delay, no ordinary five- +minute expiry, slow damage every 10,000 loaded ticks, and water/rain damage, +matching 1.21.1. Book components and the decay counter persist with the entity. +26.3 pickup particles use the extracted entity render state; 1.21.1's custom +`ItemEntity.copy()` workaround is not needed on this target. + +Test travel from both hands with each book type, return to recover the open +book, verify pages face upward, and check pickup animation and restart. +Attempt a blocked/invalid link and confirm the book stays in hand. With +Disarm, the active book should use its model while other items drop normally. +With automatic Return Links enabled, the new Return Link should arrive with +the player. Test rain/water damage with disposable books only. + +Build and disposable dedicated-server checks passed both hands, failed travel, +exception recovery, cancelled entity spawning, successful hand removal, saved +book components/lifetime, and a full restart with both book entities restored. +The fixture uses a fake player and does not prove client rendering or actual +cross-dimension travel. Entity loading is asynchronous: querying in the same +command batch as chunk loading can briefly see zero books. The server was +stopped and its world removed after validation. Operator fixtures are +`age create_dropped_fixture ` and `age dropped_probe `. + +### Notebook and Collation Folder + +The local `1.0.1.102-beta.1` slice adapts the 1.21.1 Notebook and Collation +Folder, their server menu, persistent page component and existing page editor. +Notebook capacity is 252 positions; Folder capacity is 27. Sparse positions, +dark empty pages through the last occupied position, search, scrolling, +single-page movement, shift transfers and the held-item lock are retained. +Search consumes inventory keys while focused. Large pages keep the native +30-by-40 atlas proportions and use the existing glyph-only item renderer. +Unsupported symbol pages receive the port's red tint and retain their items. + +Both collections appear in Creative. Notebook crafting reuses the 1.21.1 +leather/string pattern. The Folder has no recipe in 1.21.1 and receives none +here; discovery/library loot is still pending. Link Panels cannot be inserted +through the collection menu. Unknown symbol pages remain stored and are +counted in the tooltip. Writing Desk integration, sealed notebooks, Symbol +Portfolios and bulk Binder interactions remain later work. + +Test both collection types and hands. Insert and remove pages by normal and +shift clicks, place a page near the final position, move it with left/right +arrows after selection, scroll/search, and type E in the search field. Verify +Link Panels are rejected and the held collection cannot be moved/swapped. +Close/reopen, put the collection in a chest, restart and recover its contents. +Compare large glyphs and page outlines with the 1.21.1 editor. +Build and disposable-server checks passed both hands and capacities, sparse +positions, page movement, shift transfers through the actual menu input path, +Link Panel/non-Page rejection, held-item pickup locking, save/network codec +round trips, and all four collections stored in a chest across full restart. +The recipe and adapted recipe-unlock advancement loaded successfully. Shared +assets were checked in the JAR. No client joined these fixtures, so appearance, +mouse handling and search keys still need the in-game check. The test server +was stopped and every disposable test world removed. + +The local `1.0.1.103-beta.1` follow-up fixes the Notebook/Folder keyboard port: +26.3 uses different key values from the old GLFW-based implementation. Escape +now delegates to normal container closing even with search focused, while +left/right page movement uses the current key helpers. Search still consumes +ordinary inventory/hotbar keys. Check Escape with search focused and while +holding a page on the cursor; closing should use normal carried-item recovery. + +### Collection imports in the Book Binder + +The local `1.0.1.104-beta.1` slice adapts 1.21.1's transactional collection +import. Shift-click a Notebook or Collation Folder in the Binder inventory +to append its pages. Carry one on the cursor and click the page strip to +insert its pages before that position; clicking the panel position inserts +at the beginning. Empty collection positions are skipped, preserving the +relative order of occupied pages. Later Binder pages shift to make room. + +The import validates every page and total capacity before changing anything. +A full Binder, invalid insertion point, empty collection, or conflicting +Link Panel rejects the transaction without consuming pages. Successful +imports leave the empty collection on the cursor or in its inventory slot, +retaining its other components. Ordinary collection menus still reject Link +Panels; the Binder retains 1.21.1's handling for explicitly supplied collection +data containing a panel. Dedicated stored-collection input/UI remains unported. + +Test shift-click append and cursor insertion for both collection types, using +distinct ordered pages and gaps. Check that the collection empties only after +success and that rejected imports leave both sides unchanged. Bind a book +with the imported pages, inspect its symbols, and restart with pages in the +Binder and collections in a chest to check persistence. +Build and disposable-server checks passed full-capacity and panel-conflict +rejection, invalid positions, sparse cursor insertion, shift append, empty-cover +retention, real Binder assembly and book page order. The empty collections and +finished book survived full restart in a chest. No client joined this fixture; +cursor/shift UI interaction remains the in-game check. The server was stopped +and its world removed. Include collection imports in the future JEI guide port. + +### Symbol Portfolios and Sealed Notebooks + +The local `1.0.1.105-beta.1` slice ports the remaining collection items from +1.21.1. The main Creative tab offers prefilled Symbol Portfolios for every +symbol-page category, including registered biome/material/fluid pages. +Categories exceeding 252 pages split into named volumes without truncation. +Portfolios use the Notebook editor and Binder collection import and can be +emptied/refilled like ordinary collections. No portfolio recipe is added. + +A Sealed Notebook opens server-side into a Collation Folder with 16 random +pages. As in 1.21.1, the candidate pool excludes IDs under `block/` and +`biome/` and the corrupted category; repeated draws are allowed. This also +excludes biome-controller IDs under `biome/`, matching the existing code. +The pool is not restricted to already-ported effects, so unsupported pages +retain their red tint. Survival consumes one sealed item; Creative retains +it. The last item becomes the Folder in hand, otherwise the Folder goes into +inventory or drops if there is no space. Sealed Notebooks appear in Creative; +their discovery/library loot remains pending, and no crafting recipe is added. + +Test portfolio names, volumes, contents, editor and Binder import. Open sealed +stacks from both hands in Survival, including with full inventory, and confirm +each Folder has 16 pages. Check Creative retains the sealed item. Put the +resulting items in a chest, restart and revisit. +Build and disposable-server checks passed the 252/253-page boundary, complete +base-registry category coverage/order, seeded draws and exclusions, both-hand +opening, Survival/Creative consumption, full-inventory delivery, and portfolio/ +Folder persistence in a chest across restart. Shared models/textures were +verified in the JAR. The server was stopped and its world removed. No client +joined those fixtures; Creative presentation, editor and Binder interaction +remain the in-game check. Future JEI collection guides should cover these items. + +### Writing Desk + +The local `1.0.1.106-beta.1` slice adapts the 1.21.1 Writing Desk, its +two-block model, inventory, server catalog and existing GUI. It reuses the +Legacy desk/scrollbar/page atlases and current block textures. Selected pages +and white paper stacks render on the desk using the existing offsets. + +There are 88 collection tabs, cycled four at a time. Each holds a loose Page +or a collection with its normal capacity. Search, AZ sorting, ALL view, +category filtering, page selection, movement and extraction retain the +1.21.1 menu rules. Link Panels cannot be stored or copied, and no Link Panel +creation recipe is added to the desk. Unknown pages remain recoverable but +cannot be copied while unresolved. Unsupported port effects remain red. + +Supply paper and Ink Vials, select a known symbol, then take its virtual output +to copy it. Previewing consumes nothing; taking a copy consumes one paper and +one ink by default. `[writing] symbol_ink_cost` in `rivencraft-local.toml` +retains the 1.21.1 configurable cost. Vials fill a 16-unit meter and return +vanilla glass bottles. Refilling is event-driven and requires bottle space; +there is no refill server ticker. Originals remain in their collection. + +The desk recipe and its recipe-unlock advancement are adapted to 26.3. +New Minecraft handles Container drops during block-entity removal, so the +old onRemove drop call is replaced by partner cleanup without duplicate +inventory drops. The GUI uses current input helpers, including Escape while +search is focused. The same mouse-button correction fixes Notebook right-click +and scrollbar dragging. Progression awards and JEI guides remain later work. + +Test placement/orientation and opening from both halves, paper/selected-page +rendering and neighboring block visibility. Check the atlas layout and tab +numerals, cycling 0–87, searching/sorting/ALL, page dragging into inventory, +and copying with paper/ink. Confirm costs occur only on pickup, bottles block +refill when full, and removing bottle space allows refill. Restart with loaded +collections and a selected page. Break either half and check drops once. + +Build and disposable-server checks passed copy preview/pickup costs, +inventory-full rejection, Link Panel rejection, sparse page movement/extraction, +88-tab reachability, all four facings and both halves' removal/drop counts. +Collections, active tab, ink, paper and display page survived full restart. +Recipe/advancement data loaded; shared models and atlases were verified in +the JAR. No client joined these fixtures: visuals, mouse and live catalog +updates remain the in-game check. The server was stopped and world removed. + From the repository root: ```sh diff --git a/ports/mc263/RELEASE_NOTES_1.0.1.106-beta.1.md b/ports/mc263/RELEASE_NOTES_1.0.1.106-beta.1.md new file mode 100644 index 0000000..b63773a --- /dev/null +++ b/ports/mc263/RELEASE_NOTES_1.0.1.106-beta.1.md @@ -0,0 +1,85 @@ +# Rivencraft 26.3 beta 1.0.1.106-beta.1 + +Collects the player-validated port slices .99–.106 since 1.0.1.98-beta.1. +This beta is not yet feature-complete with Minecraft 1.21.1. + +## Books and travel + +- Fallback Age platforms now use the dedicated non-collectible platform block + from 1.21.1: opaque crystal texture, glass-like breaking strength, no drops + or pick-block item. Existing ordinary Crystal Block platforms are unchanged. +- Ported automatic Return Links for successful handheld Descriptive Book + travel. `descriptive_books.return_link_mode` in `config/rivencraft-local.toml` + defaults to **DISABLED**. `ON_CREATION` issues one on the first arrival per + Age/world with that mode enabled; `ALWAYS` issues one on each arrival. +- Return Links preserve departure dimension, position and facing. They are + issued after Disarm and drop at the destination if inventory is full. + First-arrival records persist across reload, reseeding and restart. +- Successful handheld Linking and Descriptive Book travel now leaves the + active book at departure as a recoverable open-book entity. Both hands are + supported. Failed travel or canceled entity spawning restores the book. +- Dropped books reuse the existing textures and skyward-facing model. They + have a one-second pickup delay, no ordinary five-minute expiry, slow decay + and water/rain damage, matching 1.21.1. Their components and decay counter + persist through restart. + +## Page collections and binding + +- Ported Notebooks and Collation Folders with their existing editor and assets, + capacities of 252 and 27 positions, sparse page storage, search, scrolling, + page movement and inventory transfers. Link Panels are rejected by the editor. +- Notebook crafting uses the existing leather/string pattern. The held + collection is locked against ordinary menu movement. Escape closes the menu + with search focused; search still consumes inventory/hotbar shortcuts. +- Corrected the port's keyboard and mouse-button mappings, including arrow + movement, right-click handling and scrollbar dragging. +- The Book Binder accepts collection imports: shift-click to append pages or + click a carried collection on the strip to insert them. Empty positions are + skipped and occupied page order is retained. Imports validate all inputs and + capacity before committing; successful imports return the empty collection. +- Added category Symbol Portfolios to Creative, including registry-derived + biome, material and fluid pages. Large categories split into volumes of up + to 252 pages. Portfolios use the editor and Binder import flow. +- Sealed Notebooks open into Folders containing 16 random pages using the + 1.21.1 pool/exclusions. Survival consumes one; Creative retains the item. + Full inventories receive a dropped Folder. No new recipes are invented for + Folders, Portfolios or Sealed Notebooks; discovery/library loot is pending. + +## Writing Desk + +- Ported the two-block Writing Desk, its existing model/textures, selected-page + and white paper-stack display, and Legacy GUI atlases. +- Ported 88 tabs cycled four at a time, category/search/sort/ALL views, collection + and loose-page storage, page movement/extraction, and server-owned catalog + synchronization. Link Panels cannot be stored or copied in the desk. +- Copy previews are virtual. Taking a copy consumes one paper and one ink by + default while retaining the original page. `writing.symbol_ink_cost` is + configurable in `rivencraft-local.toml`. +- Vials fill a 16-unit meter and return vanilla glass bottles. Refills are + event-driven and require bottle space. Full inventory prevents shift-copy + consumption. Unknown pages are retained but cannot be copied unresolved. +- Ported crafting and adapted its recipe-unlock advancement to 26.3. + Removing either desk half removes its partner and drops contents once. +- Further corrections to the desk model, focused on page/paper placement, + remain tracked for both versions in issue #113. + +## Compatibility and validation + +- Requires Minecraft **26.3**, NeoForge **26.3.0.99**, and **Java 25**. Dynamic + Dimensions and Bad Packets are not required. The 1.21.1 implementation is + unchanged. Use matching client/server builds; Age catalog protocol remains 10. +- Player in-game checks passed. Both build targets passed. Targeted disposable + server fixtures covered ownership recovery, collection/copy transactions, + save/network codecs, block removal, reload/reseed and restart persistence. + Servers were stopped and test worlds removed. This was not a full GameTest run. +- Full writing recovery, remaining discovery/stations, instability, crystal + portals, progression awards and optional integration parity remain unfinished. + JEI guides and discovery loot for the newly ported interactions are pending. + Unsupported effects remain marked red; Creative availability is not parity. +- Back up saves. New Native Cave profiles from .97 onward cannot be read by + earlier betas, and newly ported items/entities also require the newer build. + Keep 1.21.1 and 26.3 saves separate. Machine acceleration (#112) and Native + Cave default biome-pool policy (#111) remain separate planned work. + +Report issues and help improve the wiki at +[BlackSheep Git](https://git.paddedcell.org/dnorberg/rivencraft). diff --git a/ports/mc263/build.gradle b/ports/mc263/build.gradle index 1fa9860..6b40c08 100644 --- a/ports/mc263/build.gradle +++ b/ports/mc263/build.gradle @@ -203,6 +203,23 @@ tasks.named('processResources', ProcessResources).configure { include 'assets/rivencraft/textures/gui/symbolcomponents.png' include 'assets/rivencraft/models/item/page_link_panel.json' include 'assets/rivencraft/textures/item/ink_vial.png' + include 'assets/rivencraft/textures/item/notebook.png' + include 'assets/rivencraft/textures/item/collation_folder.png' + include 'assets/rivencraft/models/item/notebook.json' + include 'assets/rivencraft/models/item/collation_folder.json' + include 'assets/rivencraft/textures/item/sealed_notebook.png' + include 'assets/rivencraft/textures/item/symbol_portfolio.png' + include 'assets/rivencraft/models/item/sealed_notebook.json' + include 'assets/rivencraft/models/item/symbol_portfolio.json' + include 'assets/rivencraft/textures/item/writing_desk.png' + include 'assets/rivencraft/textures/block/writing_desk.png' + include 'assets/rivencraft/textures/gui/writing_desk_legacy.png' + include 'assets/rivencraft/textures/gui/scrollbar.png' + include 'assets/rivencraft/models/item/writing_desk.json' + include 'assets/rivencraft/models/block/writing_desk.json' + include 'assets/rivencraft/models/block/writing_desk_extension.json' + include 'assets/rivencraft/blockstates/writing_desk.json' + include 'data/rivencraft/loot_table/blocks/writing_desk.json' 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' @@ -215,6 +232,8 @@ tasks.named('processResources', ProcessResources).configure { include 'assets/rivencraft/textures/gui/notebook_pages.png' include 'assets/rivencraft/textures/gui/bookui_pager.png' include 'assets/rivencraft/textures/block/crystal_block.png' + include 'assets/rivencraft/textures/entity/linking_book.png' + include 'assets/rivencraft/textures/entity/descriptive_book.png' include 'assets/rivencraft/textures/block/book_binder.png' include 'assets/rivencraft/textures/block/book_binder_press.png' include 'assets/rivencraft/textures/gui/book_binder_legacy.png' @@ -233,6 +252,8 @@ tasks.named('processResources', ProcessResources).configure { include 'data/rivencraft/loot_table/blocks/book_binder.json' include 'assets/rivencraft/models/block/crystal_block.json' include 'assets/rivencraft/blockstates/crystal_block.json' + include 'assets/rivencraft/models/block/cloud_platform.json' + include 'assets/rivencraft/blockstates/cloud_platform.json' include 'assets/rivencraft/blockstates/budding_crystal.json' include 'assets/rivencraft/blockstates/small_crystal_bud.json' include 'assets/rivencraft/blockstates/medium_crystal_bud.json' diff --git a/ports/mc263/gradle.properties b/ports/mc263/gradle.properties index 53fff0b..69d253c 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.98-beta.1 +mod_version=1.0.1.106-beta.1 diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/PortConfig.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/PortConfig.java index ec1b411..5f5aa48 100644 --- a/ports/mc263/src/main/java/net/dnorberg/rivencraft/PortConfig.java +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/PortConfig.java @@ -2,14 +2,26 @@ package net.dnorberg.rivencraft; import net.neoforged.neoforge.common.ModConfigSpec; -/** Server-side limits shared with the 1.21.1 Accelerated implementation. */ +/** Server-owned gameplay policies adapted from 1.21.1. */ public final class PortConfig { + public enum ReturnLinkMode { ON_CREATION, ALWAYS, DISABLED } public static final ModConfigSpec SPEC; + public static final ModConfigSpec.IntValue SYMBOL_INK_COST; + public static final ModConfigSpec.EnumValue RETURN_LINK_MODE; public static final ModConfigSpec.IntValue ACCELERATED_MAX_CHUNKS; public static final ModConfigSpec.IntValue ACCELERATED_MAX_ATTEMPTS; static { var builder = new ModConfigSpec.Builder(); + builder.push("descriptive_books"); + RETURN_LINK_MODE = builder.comment("Controls automatic Return Links for successful Descriptive Book travel.", + "ON_CREATION issues one on the first arrival per Age/world; ALWAYS issues one each arrival; DISABLED issues none.") + .defineEnum("return_link_mode", ReturnLinkMode.DISABLED); + builder.pop(); + builder.push("writing"); + SYMBOL_INK_COST = builder.comment("Ink units consumed for one Writing Desk symbol copy.") + .defineInRange("symbol_ink_cost", 1, 1, 16); + builder.pop(); builder.push("accelerated_age"); ACCELERATED_MAX_CHUNKS = builder.comment("Maximum already-loaded player-nearby chunks given extra random ticks per Age tick.") .defineInRange("max_loaded_chunks", 8, 1, 64); 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 6f29fe3..0cbc848 100644 --- a/ports/mc263/src/main/java/net/dnorberg/rivencraft/Rivencraft.java +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/Rivencraft.java @@ -57,6 +57,15 @@ 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> ENTITIES = + DeferredRegister.create(Registries.ENTITY_TYPE, MOD_ID); + public static final Supplier> + DROPPED_BOOK = ENTITIES.register("dropped_book", () -> net.minecraft.world.entity.EntityType.Builder + .of( + net.dnorberg.rivencraft.entity.DroppedBookEntity::new, net.minecraft.world.entity.MobCategory.MISC) + .sized(0.65F, 0.4F).clientTrackingRange(10).updateInterval(20) + .build(net.minecraft.resources.ResourceKey.create(Registries.ENTITY_TYPE, + Identifier.fromNamespaceAndPath(MOD_ID, "dropped_book")))); private static final DeferredRegister SOUNDS = DeferredRegister.create(Registries.SOUND_EVENT, MOD_ID); public static final Supplier LINK = sound("linking.link"); @@ -82,6 +91,10 @@ public final class Rivencraft { public static final Supplier> AGE_BOOK_ID = COMPONENTS.registerComponentType( "age_book_id", builder -> builder.persistent(Identifier.CODEC) .networkSynchronized(Identifier.STREAM_CODEC)); + public static final Supplier> NOTEBOOK_CONTENTS = + COMPONENTS.registerComponentType("notebook_contents", builder -> builder + .persistent(net.dnorberg.rivencraft.writing.NotebookContents.CODEC) + .networkSynchronized(net.dnorberg.rivencraft.writing.NotebookContents.STREAM_CODEC)); public static final Supplier> LINK_BOOK_TARGET = COMPONENTS.registerComponentType( "link_book_target", builder -> builder.persistent(LinkBookTarget.CODEC) .networkSynchronized(LinkBookTarget.STREAM_CODEC)); @@ -110,6 +123,10 @@ public final class Rivencraft { public static final DeferredBlock CRYSTAL_BLOCK = BLOCKS.registerBlock("crystal_block", Block::new, () -> BlockBehaviour.Properties.ofFullCopy(Blocks.AMETHYST_BLOCK)); + public static final DeferredBlock CLOUD_PLATFORM = + BLOCKS.registerBlock("cloud_platform", net.dnorberg.rivencraft.block.CloudPlatformBlock::new, + () -> BlockBehaviour.Properties.ofFullCopy(Blocks.AMETHYST_BLOCK) + .mapColor(MapColor.COLOR_BLUE).strength(0.3F).noLootTable()); public static final DeferredBlock STAR_FISSURE = BLOCKS.registerBlock("star_fissure", StarFissureBlock::new, () -> BlockBehaviour.Properties.ofFullCopy(Blocks.END_PORTAL).lightLevel(state -> 6)); @@ -134,6 +151,25 @@ public final class Rivencraft { new BlockEntityType<>(InkMixerBlockEntity::new, INK_MIXER.get())); public static final Supplier> INK_MIXER_MENU = MENUS.register("ink_mixer", () -> IMenuTypeExtension.create(InkMixerMenu::new)); + public static final DeferredBlock WRITING_DESK = + BLOCKS.registerBlock("writing_desk", net.dnorberg.rivencraft.block.WritingDeskBlock::new, + () -> BlockBehaviour.Properties.ofFullCopy(Blocks.CRAFTING_TABLE).noOcclusion()); + public static final DeferredItem WRITING_DESK_ITEM = ITEMS.registerSimpleBlockItem(WRITING_DESK); + public static final Supplier> WRITING_DESK_BLOCK_ENTITY = + BLOCK_ENTITY_TYPES.register("writing_desk", () -> new BlockEntityType<>( + net.dnorberg.rivencraft.block.entity.WritingDeskBlockEntity::new, WRITING_DESK.get())); + public static final Supplier> WRITING_DESK_MENU = + MENUS.register("writing_desk", () -> IMenuTypeExtension.create(net.dnorberg.rivencraft.menu.WritingDeskMenu::new)); + public static final Supplier> NOTEBOOK_MENU = + MENUS.register("notebook", () -> IMenuTypeExtension.create(net.dnorberg.rivencraft.menu.NotebookMenu::new)); + public static final DeferredItem NOTEBOOK = + ITEMS.registerItem("notebook", net.dnorberg.rivencraft.item.NotebookItem::new, properties -> properties.stacksTo(1)); + public static final DeferredItem COLLATION_FOLDER = + ITEMS.registerItem("collation_folder", net.dnorberg.rivencraft.item.CollationFolderItem::new, properties -> properties.stacksTo(1)); + public static final DeferredItem SYMBOL_PORTFOLIO = + ITEMS.registerItem("symbol_portfolio", net.dnorberg.rivencraft.item.SymbolPortfolioItem::new, properties -> properties.stacksTo(1)); + public static final DeferredItem SEALED_NOTEBOOK = + ITEMS.registerItem("sealed_notebook", net.dnorberg.rivencraft.item.SealedNotebookItem::new, properties -> properties.stacksTo(16)); public static final DeferredItem CRYSTAL_BLOCK_ITEM = ITEMS.registerSimpleBlockItem(CRYSTAL_BLOCK); public static final DeferredBlock BUDDING_CRYSTAL = BLOCKS.registerBlock( "budding_crystal", BuddingCrystalBlock::new, @@ -171,6 +207,7 @@ public final class Rivencraft { BLOCKS.register(modEventBus); SOUNDS.register(modEventBus); ITEMS.register(modEventBus); + ENTITIES.register(modEventBus); BLOCK_ENTITY_TYPES.register(modEventBus); MENUS.register(modEventBus); COMPONENTS.register(modEventBus); diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortAgeCommands.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortAgeCommands.java index d4c422f..32afcc2 100644 --- a/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortAgeCommands.java +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortAgeCommands.java @@ -69,6 +69,117 @@ public final class PortAgeCommands { return 1; }))) .then(Commands.literal("age") + .then(Commands.literal("create_writing_desk_fixture") + .then(Commands.argument("name", StringArgumentType.word()) + .then(Commands.argument("seed", LongArgumentType.longArg()) + .executes(context -> { + var level = RuntimeAgeBackend.create(context.getSource().getServer(), ageId(context), + LongArgumentType.getLong(context, "seed"), null, PortAgeTerrain.VOID); + PortWritingDeskProbe.create(level); + context.getSource().sendSuccess(() -> Component.literal("Writing Desk transaction/removal checks passed"), false); + return 1; + })))) + .then(Commands.literal("writing_desk_probe") + .then(Commands.argument("name", StringArgumentType.word()) + .executes(context -> { + var level = context.getSource().getServer().getLevel(ResourceKey.create(Registries.DIMENSION, ageId(context))); + if (level == null) throw new IllegalArgumentException("Age not loaded"); + PortWritingDeskProbe.verify(level); + context.getSource().sendSuccess(() -> Component.literal("Writing Desk persistence passed"), false); + return 1; + }))) + .then(Commands.literal("create_collection_extras_fixture") + .then(Commands.argument("name", StringArgumentType.word()) + .then(Commands.argument("seed", LongArgumentType.longArg()) + .executes(context -> { + var level = RuntimeAgeBackend.create(context.getSource().getServer(), ageId(context), + LongArgumentType.getLong(context, "seed"), null, PortAgeTerrain.VOID); + PortCollectionExtrasProbe.create(level); + context.getSource().sendSuccess(() -> Component.literal("Portfolio/Sealed Notebook checks passed"), false); + return 1; + })))) + .then(Commands.literal("collection_extras_probe") + .then(Commands.argument("name", StringArgumentType.word()) + .executes(context -> { + var level = context.getSource().getServer().getLevel(ResourceKey.create(Registries.DIMENSION, ageId(context))); + if (level == null) throw new IllegalArgumentException("Age not loaded"); + PortCollectionExtrasProbe.verify(level); + context.getSource().sendSuccess(() -> Component.literal("Portfolio/Sealed Notebook persistence passed"), false); + return 1; + }))) + .then(Commands.literal("create_binder_collection_fixture") + .then(Commands.argument("name", StringArgumentType.word()) + .then(Commands.argument("seed", LongArgumentType.longArg()) + .executes(context -> { + var level = RuntimeAgeBackend.create(context.getSource().getServer(), ageId(context), + LongArgumentType.getLong(context, "seed"), null, PortAgeTerrain.VOID); + PortBinderCollectionProbe.create(level); + context.getSource().sendSuccess(() -> Component.literal("Binder collection transaction/assembly checks passed"), false); + return 1; + })))) + .then(Commands.literal("binder_collection_probe") + .then(Commands.argument("name", StringArgumentType.word()) + .executes(context -> { + var level = context.getSource().getServer().getLevel(ResourceKey.create(Registries.DIMENSION, ageId(context))); + if (level == null) throw new IllegalArgumentException("Age not loaded"); + PortBinderCollectionProbe.verify(level); + context.getSource().sendSuccess(() -> Component.literal("Binder collection persistence passed"), false); + return 1; + }))) + .then(Commands.literal("create_notebook_fixture") + .then(Commands.argument("name", StringArgumentType.word()) + .then(Commands.argument("seed", LongArgumentType.longArg()) + .executes(context -> { + var level = RuntimeAgeBackend.create(context.getSource().getServer(), ageId(context), + LongArgumentType.getLong(context, "seed"), null, PortAgeTerrain.VOID); + PortNotebookProbe.create(level); + context.getSource().sendSuccess(() -> Component.literal("Notebook menu/codec checks passed"), false); + return 1; + })))) + .then(Commands.literal("notebook_probe") + .then(Commands.argument("name", StringArgumentType.word()) + .executes(context -> { + var level = context.getSource().getServer().getLevel(ResourceKey.create(Registries.DIMENSION, ageId(context))); + if (level == null) throw new IllegalArgumentException("Age not loaded"); + PortNotebookProbe.verify(level); + context.getSource().sendSuccess(() -> Component.literal("Notebook chest persistence passed"), false); + return 1; + }))) + .then(Commands.literal("create_dropped_fixture") + .then(Commands.argument("name", StringArgumentType.word()) + .then(Commands.argument("seed", LongArgumentType.longArg()) + .executes(context -> { + var level = RuntimeAgeBackend.create(context.getSource().getServer(), ageId(context), + LongArgumentType.getLong(context, "seed"), null, PortAgeTerrain.VOID); + PortDroppedBookProbe.create(level); + context.getSource().sendSuccess(() -> Component.literal("Dropped book ownership/codec checks passed"), false); + return 1; + })))) + .then(Commands.literal("dropped_probe") + .then(Commands.argument("name", StringArgumentType.word()) + .executes(context -> { + var level = context.getSource().getServer().getLevel( + ResourceKey.create(Registries.DIMENSION, ageId(context))); + if (level == null) throw new IllegalArgumentException("Age not loaded"); + level.getChunk(0, 0); + PortDroppedBookProbe.verify(level); + context.getSource().sendSuccess(() -> Component.literal("Dropped book persistence checks passed"), false); + return 1; + }))) + .then(Commands.literal("create_return_fixture") + .then(Commands.argument("name", StringArgumentType.word()) + .then(Commands.argument("seed", LongArgumentType.longArg()) + .executes(PortAgeCommands::createReturnFixture)))) + .then(Commands.literal("return_link_status") + .then(Commands.argument("name", StringArgumentType.word()) + .executes(PortAgeCommands::returnLinkStatus))) + .then(Commands.literal("create_platform") + .then(Commands.argument("name", StringArgumentType.word()) + .then(Commands.argument("seed", LongArgumentType.longArg()) + .executes(PortAgeCommands::createPlatform)))) + .then(Commands.literal("platform_probe") + .then(Commands.argument("name", StringArgumentType.word()) + .executes(PortAgeCommands::probePlatform))) .then(Commands.literal("create_accelerated") .then(Commands.argument("name", StringArgumentType.word()) .then(Commands.argument("seed", LongArgumentType.longArg()) @@ -2146,6 +2257,85 @@ public final class PortAgeCommands { return 1; } + private static int createReturnFixture(CommandContext context) { + Identifier id = ageId(context); + var local = new PortAgeVisits(java.util.List.of()); + var disabled = net.dnorberg.rivencraft.PortConfig.ReturnLinkMode.DISABLED; + var always = net.dnorberg.rivencraft.PortConfig.ReturnLinkMode.ALWAYS; + var once = net.dnorberg.rivencraft.PortConfig.ReturnLinkMode.ON_CREATION; + if (PortReturnLinks.shouldIssue(disabled, local, id) || local.hasVisited(id) + || !PortReturnLinks.shouldIssue(always, local, id) + || !PortReturnLinks.shouldIssue(always, local, id) || local.hasVisited(id) + || !PortReturnLinks.shouldIssue(once, local, id) + || PortReturnLinks.shouldIssue(once, local, id)) + throw new IllegalStateException("Return Link mode regression"); + var encoded = PortAgeVisits.CODEC.encodeStart(com.mojang.serialization.JsonOps.INSTANCE, local).getOrThrow(); + var decoded = PortAgeVisits.CODEC.parse(com.mojang.serialization.JsonOps.INSTANCE, encoded).getOrThrow(); + if (!decoded.hasVisited(id) || PortReturnLinks.shouldIssue(once, decoded, id)) + throw new IllegalStateException("Return Link visit codec regression"); + var target = new LinkBookTarget(Identifier.fromNamespaceAndPath("minecraft", "overworld"), + 12.25, 80.5, -42.75, 135.0F, -20.0F); + var book = PortReturnLinks.createBook(target); + if (!book.is(Rivencraft.LINKING_BOOK.get()) + || !target.equals(book.get(Rivencraft.LINK_BOOK_TARGET.get())) + || !book.getHoverName().getString().equals("Return Link") + || book.has(Rivencraft.LINK_PROPERTIES.get())) + throw new IllegalStateException("Return Link book component regression"); + var server = context.getSource().getServer(); + RuntimeAgeBackend.create(server, id, LongArgumentType.getLong(context, "seed"), null, PortAgeTerrain.VOID); + var visits = server.overworld().getDataStorage().computeIfAbsent(PortAgeVisits.TYPE); + if (!PortReturnLinks.shouldIssue(once, visits, id) || PortReturnLinks.shouldIssue(once, visits, id)) + throw new IllegalStateException("Return Link world marker regression"); + server.overworld().getDataStorage().saveAndJoin(); + return returnLinkStatus(context); + } + + private static int returnLinkStatus(CommandContext context) { + var visits = context.getSource().getServer().overworld().getDataStorage().computeIfAbsent(PortAgeVisits.TYPE); + context.getSource().sendSuccess(() -> Component.literal(ageId(context) + " Return Link mode=" + + net.dnorberg.rivencraft.PortConfig.RETURN_LINK_MODE.get() + + "; ON_CREATION issued=" + visits.hasVisited(ageId(context))), false); + return 1; + } + + private static int createPlatform(CommandContext context) { + ServerLevel level = RuntimeAgeBackend.create(context.getSource().getServer(), ageId(context), + LongArgumentType.getLong(context, "seed"), null, PortAgeTerrain.VOID); + var landing = PortAgeTravel.findLanding(level); + if (!new net.minecraft.core.BlockPos(0, 64, 0).equals(landing)) + throw new IllegalStateException("Unexpected Void landing: " + landing); + return probePlatform(context); + } + + /** Inspect the disposable Void fixture without regenerating or repairing its platform. */ + private static int probePlatform(CommandContext context) { + ServerLevel level = context.getSource().getServer().getLevel( + ResourceKey.create(Registries.DIMENSION, ageId(context))); + if (level == null) { + context.getSource().sendFailure(Component.literal("Age not loaded: " + ageId(context))); + return 0; + } + for (int x = -1; x <= 1; x++) for (int z = -1; z <= 1; z++) { + var pos = new net.minecraft.core.BlockPos(x, 63, z); + var state = level.getBlockState(pos); + if (!state.is(Rivencraft.CLOUD_PLATFORM.get()) + || state.getDestroySpeed(level, pos) != 0.3F + || !state.isSolidRender() || !state.isCollisionShapeFullBlock(level, pos) + || !state.getCloneItemStack(level, pos, false).isEmpty() + || !net.minecraft.world.level.block.Block.getDrops(state, level, pos, null).isEmpty()) + throw new IllegalStateException("Platform properties mismatch at " + pos); + } + Identifier id = Identifier.fromNamespaceAndPath("rivencraft", "cloud_platform"); + if (net.dnorberg.rivencraft.writing.PortRegistryPages.solidMaterialIds().contains(id) + || net.dnorberg.rivencraft.writing.PortRegistryPages.find( + net.dnorberg.rivencraft.writing.PortRegistryPages.blockPageId(id)) != null + || !Rivencraft.CLOUD_PLATFORM.get().asItem().equals(net.minecraft.world.item.Items.AIR)) + throw new IllegalStateException("Platform is obtainable as an item or material Page"); + context.getSource().sendSuccess(() -> Component.literal(ageId(context) + + " platform passed: solid/opaque, strength 0.3, no loot/pick item/material Page"), false); + return 1; + } + private static int createAccelerated(CommandContext context) { int count = IntegerArgumentType.getInteger(context, "pages"); var pages = new java.util.ArrayList(); diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortAgeTravel.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortAgeTravel.java index 87a91c5..105f39d 100644 --- a/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortAgeTravel.java +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortAgeTravel.java @@ -37,7 +37,7 @@ public final class PortAgeTravel { return moved ? null : "Teleportation failed; remaining in source dimension"; } - private static BlockPos findLanding(ServerLevel target) { + static BlockPos findLanding(ServerLevel target) { if (isCaveTerrain(target)) { // The cave roof is not a valid arrival surface. Search down from // Legacy's Y=64 start, then construct the normal safety platform @@ -112,7 +112,7 @@ public final class PortAgeTravel { } for (int x = -1; x <= 1; x++) { for (int z = -1; z <= 1; z++) { - target.setBlock(foot.offset(x, -1, z), Rivencraft.CRYSTAL_BLOCK.get().defaultBlockState(), 3); + target.setBlock(foot.offset(x, -1, z), Rivencraft.CLOUD_PLATFORM.get().defaultBlockState(), 3); } } target.setBlock(foot, Blocks.AIR.defaultBlockState(), 3); diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortAgeVisits.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortAgeVisits.java new file mode 100644 index 0000000..8fbc63a --- /dev/null +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortAgeVisits.java @@ -0,0 +1,27 @@ +package net.dnorberg.rivencraft.age; + +import com.mojang.serialization.Codec; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import net.minecraft.resources.Identifier; +import net.minecraft.world.level.saveddata.SavedData; +import net.minecraft.world.level.saveddata.SavedDataType; + +/** World-wide record of Ages for which an ON_CREATION Return Link was issued. */ +final class PortAgeVisits extends SavedData { + static final Codec CODEC = Identifier.CODEC.listOf().fieldOf("ages").codec() + .xmap(PortAgeVisits::new, visits -> visits.ages.stream().sorted().toList()); + static final SavedDataType TYPE = new SavedDataType<>( + Identifier.fromNamespaceAndPath("rivencraft", "age_visits"), + () -> new PortAgeVisits(List.of()), CODEC); + private final Set ages; + + PortAgeVisits(List ages) { this.ages = new HashSet<>(ages); } + boolean hasVisited(Identifier age) { return ages.contains(age); } + boolean markFirstVisit(Identifier age) { + if (!ages.add(age)) return false; + setDirty(); + return true; + } +} diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortBinderCollectionProbe.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortBinderCollectionProbe.java new file mode 100644 index 0000000..6dc4f66 --- /dev/null +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortBinderCollectionProbe.java @@ -0,0 +1,99 @@ +package net.dnorberg.rivencraft.age; + +import java.util.List; +import net.dnorberg.rivencraft.Rivencraft; +import net.dnorberg.rivencraft.block.entity.PortBookBinderBlockEntity; +import net.dnorberg.rivencraft.item.PageItem; +import net.dnorberg.rivencraft.menu.PortBookBinderMenu; +import net.dnorberg.rivencraft.writing.NotebookContents; +import net.minecraft.core.BlockPos; +import net.minecraft.resources.Identifier; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.Container; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.block.Blocks; + +/** Disposable fixture for atomic collection imports and real Binder assembly. */ +final class PortBinderCollectionProbe { + private static final BlockPos BINDER = new BlockPos(0, 64, 0); + private static final BlockPos CHEST = new BlockPos(1, 64, 0); + private static final List EXPECTED = List.of(id("weather/off"), id("lighting/bright"), + id("terrain/flat"), id("biome/single")); + private PortBinderCollectionProbe() {} + private static Identifier id(String path) { return Identifier.fromNamespaceAndPath("rivencraft", path); } + private static ItemStack page(String path) { return PageItem.createSymbolPage(id(path)); } + private static ItemStack collection(boolean folder, List pages) { + ItemStack result = new ItemStack(folder ? Rivencraft.COLLATION_FOLDER.get() : Rivencraft.NOTEBOOK.get()); + result.set(Rivencraft.NOTEBOOK_CONTENTS.get(), new NotebookContents(pages)); + return result; + } + + static void create(ServerLevel level) { + PortAgeTravel.findLanding(level); + level.setBlock(BINDER, Rivencraft.BOOK_BINDER.get().defaultBlockState(), 3); + level.setBlock(CHEST, Blocks.CHEST.defaultBlockState(), 3); + var binder = (PortBookBinderBlockEntity) level.getBlockEntity(BINDER); + var chest = (Container) level.getBlockEntity(CHEST); + ItemStack folder = collection(true, List.of(page("weather/off"), ItemStack.EMPTY, page("lighting/bright"))); + ItemStack before = folder.copy(); + for (int i = 0; i < PortBookBinderBlockEntity.PAGE_COUNT; i++) + binder.setItem(PortBookBinderBlockEntity.FIRST_PAGE + i, page("terrain/flat")); + if (!binder.importCollection(folder, 0).isEmpty() || binder.nextPageIndex() != 252 + || !ItemStack.isSameItemSameComponents(before, folder) + || binder.symbolPages().stream().anyMatch(symbol -> !symbol.equals(id("terrain/flat")))) + throw new IllegalStateException("Full Binder import was not atomic"); + binder.clearSymbolPages(); + binder.setItem(PortBookBinderBlockEntity.PANEL, PageItem.createLinkPanelPage()); + if (!binder.importCollection(collection(true, List.of(page("weather/off"), PageItem.createLinkPanelPage())), 0).isEmpty() + || binder.hasSymbolPages() || binder.getItem(PortBookBinderBlockEntity.PANEL).isEmpty()) + throw new IllegalStateException("Conflicting Link Panel import was not atomic"); + binder.setItem(PortBookBinderBlockEntity.PANEL, ItemStack.EMPTY); + if (!binder.importCollection(folder, -1).isEmpty() || !binder.importCollection(folder, 1).isEmpty()) + throw new IllegalStateException("Invalid collection insertion point accepted"); + binder.insertPage(0, page("terrain/flat")); + var player = net.neoforged.neoforge.common.util.FakePlayerFactory.get(level, + new com.mojang.authlib.GameProfile(java.util.UUID.fromString("5225a1ed-5ead-44b5-a63c-c78e7dca86f5"), "RivenBinderProbe")); + player.setPos(0.5, 64, 2.5); + var menu = new PortBookBinderMenu(1, player.getInventory(), binder); + try { + menu.setCarried(folder); + if (!menu.clickMenuButton(player, PortBookBinderMenu.STRIP_CLICK_BASE + 1) + || menu.getCarried().get(Rivencraft.NOTEBOOK_CONTENTS.get()).pageCount() != 0 + || !binder.symbolPages().equals(EXPECTED.subList(0, 3))) + throw new IllegalStateException("Cursor collection order/empty-cover regression"); + chest.setItem(0, menu.getCarried().copy()); + menu.setCarried(ItemStack.EMPTY); + player.getInventory().setItem(9, collection(false, List.of(page("biome/single")))); + menu.quickMoveStack(player, PortBookBinderBlockEntity.SIZE); + ItemStack emptied = player.getInventory().getItem(9); + if (!emptied.is(Rivencraft.NOTEBOOK.get()) + || emptied.get(Rivencraft.NOTEBOOK_CONTENTS.get()).pageCount() != 0 + || !binder.symbolPages().equals(EXPECTED)) + throw new IllegalStateException("Shift collection import regression"); + chest.setItem(1, emptied.copy()); + binder.setItem(PortBookBinderBlockEntity.COVER, new ItemStack(Items.LEATHER)); + binder.setItem(PortBookBinderBlockEntity.PANEL, PageItem.createLinkPanelPage()); + menu.assemble(player, "Collection Fixture104"); + ItemStack book = menu.getCarried(); + if (!book.is(Rivencraft.DESCRIPTIVE_BOOK.get()) || !EXPECTED.equals(book.get(Rivencraft.BOOK_SYMBOLS.get())) + || !book.has(Rivencraft.AGE_BOOK_ID.get()) || binder.hasSymbolPages() + || !binder.getItem(PortBookBinderBlockEntity.COVER).isEmpty() + || !binder.getItem(PortBookBinderBlockEntity.PANEL).isEmpty()) + throw new IllegalStateException("Collection-backed Binder assembly regression"); + chest.setItem(2, book.copy()); + menu.setCarried(ItemStack.EMPTY); + } finally { player.getInventory().clearContent(); } + verify(level); + } + + static void verify(ServerLevel level) { + if (!(level.getBlockEntity(CHEST) instanceof Container chest)) throw new IllegalStateException("Fixture chest missing"); + if (!chest.getItem(0).is(Rivencraft.COLLATION_FOLDER.get()) || !chest.getItem(1).is(Rivencraft.NOTEBOOK.get())) + throw new IllegalStateException("Empty collection covers were not preserved"); + for (int i = 0; i < 2; i++) if (chest.getItem(i).get(Rivencraft.NOTEBOOK_CONTENTS.get()).pageCount() != 0) + throw new IllegalStateException("Imported pages remained in a collection"); + if (!EXPECTED.equals(chest.getItem(2).get(Rivencraft.BOOK_SYMBOLS.get()))) + throw new IllegalStateException("Authored book page order was not preserved"); + } +} diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortCollectionExtrasProbe.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortCollectionExtrasProbe.java new file mode 100644 index 0000000..5659e8a --- /dev/null +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortCollectionExtrasProbe.java @@ -0,0 +1,109 @@ +package net.dnorberg.rivencraft.age; + +import java.util.List; +import net.dnorberg.rivencraft.Rivencraft; +import net.dnorberg.rivencraft.item.PageItem; +import net.dnorberg.rivencraft.item.SymbolPortfolioItem; +import net.dnorberg.rivencraft.writing.NotebookContents; +import net.dnorberg.rivencraft.writing.PortSymbolPages; +import net.minecraft.core.BlockPos; +import net.minecraft.resources.Identifier; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.Container; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.phys.AABB; + +/** Disposable-world boundary, booster-use and persistence checks. */ +final class PortCollectionExtrasProbe { + private static final BlockPos CHEST = new BlockPos(0, 64, 0); + private PortCollectionExtrasProbe() {} + private static Identifier id(String path) { return Identifier.fromNamespaceAndPath("rivencraft", path); } + private static NotebookContents contents(ItemStack stack) { return stack.get(Rivencraft.NOTEBOOK_CONTENTS.get()); } + private static List symbols(ItemStack collection) { + return contents(collection).pages().stream().map(page -> page.get(Rivencraft.SYMBOL_PAGE_ID.get())).toList(); + } + private static void folder(ItemStack stack) { + if (!stack.is(Rivencraft.COLLATION_FOLDER.get()) || contents(stack).pageCount() != 16 + || symbols(stack).stream().anyMatch(id -> !PortSymbolPages.sealedCandidateIds().contains(id))) + throw new IllegalStateException("Sealed Notebook page pool/count regression"); + } + + static void create(ServerLevel level) { + PortAgeTravel.findLanding(level); + level.setBlock(CHEST, Blocks.CHEST.defaultBlockState(), 3); + var chest = (Container) level.getBlockEntity(CHEST); + var pages = new java.util.ArrayList(); + for (int i = 0; i < 253; i++) pages.add(PageItem.createSymbolPage(id(i % 2 == 0 ? "terrain/flat" : "weather/off"))); + var volumes = SymbolPortfolioItem.createVolumes(id("feature"), pages); + if (volumes.size() != 2 || contents(volumes.get(0)).pageCount() != 252 + || contents(volumes.get(1)).pageCount() != 1 + || !volumes.stream().flatMap(volume -> symbols(volume).stream()).toList() + .equals(pages.stream().map(page -> page.get(Rivencraft.SYMBOL_PAGE_ID.get())).toList()) + || !SymbolPortfolioItem.createVolumes(id("feature"), List.of()).isEmpty()) + throw new IllegalStateException("Portfolio volume boundary/order regression"); + chest.setItem(0, volumes.get(0)); + chest.setItem(1, volumes.get(1)); + var actual = net.dnorberg.rivencraft.registry.ModCreativeTabs.creativeSymbolPortfolios(level.registryAccess()); + var allPages = PortSymbolPages.categories().stream().flatMap(category -> PortSymbolPages.pages(category, level.registryAccess()).stream()) + .map(page -> page.get(Rivencraft.SYMBOL_PAGE_ID.get())).toList(); + if (!actual.stream().flatMap(volume -> symbols(volume).stream()).toList().equals(allPages)) + throw new IllegalStateException("Creative portfolios omit/duplicate/reorder pages"); + var player = net.neoforged.neoforge.common.util.FakePlayerFactory.get(level, + new com.mojang.authlib.GameProfile(java.util.UUID.fromString("790b69dd-b62b-45be-9017-caf16515169a"), "RivenExtrasProbe")); + player.setPos(0.5, 64, 2.5); + try { + player.getAbilities().instabuild = false; + player.getRandom().setSeed(105); + var first = net.dnorberg.rivencraft.item.SealedNotebookItem.open(player); + player.getRandom().setSeed(105); + var repeat = net.dnorberg.rivencraft.item.SealedNotebookItem.open(player); + folder(first); + if (!symbols(first).equals(symbols(repeat))) throw new IllegalStateException("Booster seeded selection changed"); + chest.setItem(2, first); + for (var hand : InteractionHand.values()) { + player.getInventory().clearContent(); + player.setItemInHand(InteractionHand.OFF_HAND, ItemStack.EMPTY); + player.setItemInHand(hand, new ItemStack(Rivencraft.SEALED_NOTEBOOK.get())); + Rivencraft.SEALED_NOTEBOOK.get().use(level, player, hand); + folder(player.getItemInHand(hand)); + } + player.getInventory().clearContent(); + player.setItemInHand(InteractionHand.OFF_HAND, ItemStack.EMPTY); + player.setItemInHand(InteractionHand.MAIN_HAND, new ItemStack(Rivencraft.SEALED_NOTEBOOK.get(), 2)); + Rivencraft.SEALED_NOTEBOOK.get().use(level, player, InteractionHand.MAIN_HAND); + if (player.getMainHandItem().getCount() != 1) throw new IllegalStateException("Booster stack consumption regression"); + player.getAbilities().instabuild = true; + Rivencraft.SEALED_NOTEBOOK.get().use(level, player, InteractionHand.MAIN_HAND); + if (!player.getMainHandItem().is(Rivencraft.SEALED_NOTEBOOK.get()) || player.getMainHandItem().getCount() != 1) + throw new IllegalStateException("Creative booster consumed its input"); + player.getAbilities().instabuild = false; + for (int slot = 0; slot < player.getInventory().getContainerSize(); slot++) + player.getInventory().setItem(slot, new ItemStack(Items.COBBLESTONE, 64)); + player.setItemInHand(InteractionHand.MAIN_HAND, new ItemStack(Rivencraft.SEALED_NOTEBOOK.get(), 2)); + Rivencraft.SEALED_NOTEBOOK.get().use(level, player, InteractionHand.MAIN_HAND); + var dropped = level.getEntitiesOfClass(ItemEntity.class, new AABB(-3, 60, -3, 4, 70, 6), + entity -> entity.getItem().is(Rivencraft.COLLATION_FOLDER.get())); + if (dropped.size() != 1 || player.getMainHandItem().getCount() != 1) + throw new IllegalStateException("Full-inventory booster delivery regression"); + folder(dropped.getFirst().getItem()); + dropped.getFirst().discard(); + } finally { + player.getAbilities().instabuild = false; + player.getInventory().clearContent(); + player.setItemInHand(InteractionHand.OFF_HAND, ItemStack.EMPTY); + } + verify(level); + } + + static void verify(ServerLevel level) { + if (!(level.getBlockEntity(CHEST) instanceof Container chest)) throw new IllegalStateException("Fixture chest missing"); + if (!chest.getItem(0).is(Rivencraft.SYMBOL_PORTFOLIO.get()) || contents(chest.getItem(0)).pageCount() != 252 + || !chest.getItem(1).is(Rivencraft.SYMBOL_PORTFOLIO.get()) || contents(chest.getItem(1)).pageCount() != 1) + throw new IllegalStateException("Portfolio persistence regression"); + folder(chest.getItem(2)); + } +} diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortDroppedBookProbe.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortDroppedBookProbe.java new file mode 100644 index 0000000..0230273 --- /dev/null +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortDroppedBookProbe.java @@ -0,0 +1,81 @@ +package net.dnorberg.rivencraft.age; + +import java.util.UUID; +import net.dnorberg.rivencraft.Rivencraft; +import net.dnorberg.rivencraft.entity.DroppedBookEntity; +import net.dnorberg.rivencraft.item.LinkBookTarget; +import net.dnorberg.rivencraft.item.PortHandheldTravel; +import net.minecraft.core.component.DataComponents; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.Identifier; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.storage.TagValueInput; +import net.minecraft.world.level.storage.TagValueOutput; +import net.minecraft.util.ProblemReporter; +import net.minecraft.world.phys.AABB; +import net.neoforged.neoforge.common.util.FakePlayerFactory; + +/** Disposable-world checks for book ownership and the new entity save format. */ +final class PortDroppedBookProbe { + private PortDroppedBookProbe() {} + + static void create(ServerLevel level) { + PortAgeTravel.findLanding(level); + var player = FakePlayerFactory.get(level, new com.mojang.authlib.GameProfile( + UUID.fromString("4db5c544-0a0c-421d-a128-c520bda64642"), "RivenBookProbe")); + player.setPos(0.5, 64, 0.5); + try { + for (var hand : InteractionHand.values()) { + ItemStack book = new ItemStack(hand == InteractionHand.MAIN_HAND + ? Rivencraft.LINKING_BOOK.get() : Rivencraft.DESCRIPTIVE_BOOK.get()); + book.set(DataComponents.CUSTOM_NAME, Component.literal("Dropped fixture " + hand)); + book.set(Rivencraft.LINK_BOOK_TARGET.get(), new LinkBookTarget( + Identifier.fromNamespaceAndPath("minecraft", "overworld"), 12.25, 80.5, -42.75, 135, -20)); + player.setItemInHand(hand, book); + String failure = PortHandheldTravel.travel(player, hand, book, () -> "expected failure"); + if (!"expected failure".equals(failure) || player.getItemInHand(hand) != book) + throw new IllegalStateException("Failed travel consumed the book"); + try { + PortHandheldTravel.travel(player, hand, book, () -> { throw new IllegalStateException("fixture"); }); + throw new AssertionError("Fixture exception was swallowed"); + } catch (IllegalStateException expected) { + if (!"fixture".equals(expected.getMessage()) || player.getItemInHand(hand) != book) throw expected; + } + java.util.function.Consumer cancel = event -> { + if (event.getLevel() == level && event.getEntity() instanceof DroppedBookEntity) event.setCanceled(true); + }; + net.neoforged.neoforge.common.NeoForge.EVENT_BUS.addListener(cancel); + try { + failure = PortHandheldTravel.travel(player, hand, book, () -> null); + if (failure != null || player.getItemInHand(hand) != book) + throw new IllegalStateException("Rejected dropped entity consumed the book"); + } finally { net.neoforged.neoforge.common.NeoForge.EVENT_BUS.unregister(cancel); } + failure = PortHandheldTravel.travel(player, hand, book, () -> null); + if (failure != null || !player.getItemInHand(hand).isEmpty()) + throw new IllegalStateException("Successful travel retained the active book"); + } + } finally { player.getInventory().clearContent(); } + verify(level); + } + + static void verify(ServerLevel level) { + var books = level.getEntitiesOfClass(DroppedBookEntity.class, new AABB(-2, 60, -2, 3, 68, 3)); + if (books.size() != 2) throw new IllegalStateException("Expected two dropped books, found " + books.size()); + for (var entity : books) { + var output = TagValueOutput.createWithContext(ProblemReporter.DISCARDING, level.registryAccess()); + entity.saveWithoutId(output); + var copy = new DroppedBookEntity(Rivencraft.DROPPED_BOOK.get(), level); + copy.load(TagValueInput.create(ProblemReporter.DISCARDING, level.registryAccess(), output.buildResult())); + if (!ItemStack.isSameItemSameComponents(copy.getItem(), entity.getItem()) + || copy.getAge() != -32768 || copy.getItem().getCount() != 1) + throw new IllegalStateException("Dropped book persistence regression"); + var copied = TagValueOutput.createWithContext(ProblemReporter.DISCARDING, level.registryAccess()); + copy.saveWithoutId(copied); + if (copied.buildResult().getIntOr("BookDecayTicks", -1) + != output.buildResult().getIntOr("BookDecayTicks", -2)) + throw new IllegalStateException("Dropped book decay counter was not preserved"); + } + } +} diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortNotebookProbe.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortNotebookProbe.java new file mode 100644 index 0000000..de9e5d5 --- /dev/null +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortNotebookProbe.java @@ -0,0 +1,94 @@ +package net.dnorberg.rivencraft.age; + +import java.util.List; +import net.dnorberg.rivencraft.Rivencraft; +import net.dnorberg.rivencraft.item.CollationFolderItem; +import net.dnorberg.rivencraft.item.PageItem; +import net.dnorberg.rivencraft.menu.NotebookMenu; +import net.dnorberg.rivencraft.writing.NotebookContents; +import net.minecraft.core.BlockPos; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.resources.Identifier; +import net.minecraft.resources.RegistryOps; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.Container; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.inventory.ContainerInput; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Blocks; + +/** Disposable-world menu, ownership, codec and chest-persistence fixtures. */ +final class PortNotebookProbe { + private static final BlockPos CHEST = new BlockPos(0, 64, 0); + private PortNotebookProbe() {} + + static void create(ServerLevel level) { + PortAgeTravel.findLanding(level); + level.setBlock(CHEST, Blocks.CHEST.defaultBlockState(), 3); + Container chest = (Container) level.getBlockEntity(CHEST); + var player = net.neoforged.neoforge.common.util.FakePlayerFactory.get(level, + new com.mojang.authlib.GameProfile(java.util.UUID.fromString("1043b9f3-f348-4a73-b585-e0de13fd5c27"), "RivenNotebookProbe")); + int output = 0; + try { + for (boolean folder : List.of(false, true)) for (var hand : InteractionHand.values()) { + player.getInventory().clearContent(); + player.getInventory().setSelectedSlot(0); + ItemStack collection = new ItemStack(folder ? Rivencraft.COLLATION_FOLDER.get() : Rivencraft.NOTEBOOK.get()); + player.setItemInHand(hand, collection); + var menu = new NotebookMenu(1, player.getInventory(), hand); + int capacity = CollationFolderItem.capacity(collection); + if (!menu.stillValid(player) || menu.pageCapacity() != capacity + || menu.getSlot(0).mayPlace(PageItem.createLinkPanelPage()) + || menu.getSlot(0).mayPlace(new ItemStack(net.minecraft.world.item.Items.DIAMOND))) + throw new IllegalStateException("Notebook capacity/page acceptance regression"); + ItemStack page = PageItem.createSymbolPage(Identifier.fromNamespaceAndPath("rivencraft", "terrain/flat")); + player.getInventory().setItem(9, page.copyWithCount(3)); + menu.clicked(NotebookMenu.NOTEBOOK_SLOTS, 0, ContainerInput.QUICK_MOVE, player); + if (!player.getInventory().getItem(9).isEmpty() || contents(collection).pageCount() != 3) + throw new IllegalStateException("Notebook shift insertion regression"); + for (int i = 0; i < 3; i++) menu.clicked(i, 0, ContainerInput.QUICK_MOVE, player); + if (contents(collection).pageCount() != 0) throw new IllegalStateException("Notebook shift extraction regression"); + menu.getSlot(capacity - 1).set(page.copy()); + if (contents(collection).pageCount() != 1 || !contents(collection).pages().get(0).isEmpty() + || contents(collection).pages().get(capacity - 1).isEmpty()) + throw new IllegalStateException("Notebook sparse position regression"); + if (!menu.clickMenuButton(player, NotebookMenu.MOVE_PREVIOUS_BASE + capacity - 1) + || !contents(collection).pages().get(capacity - 1).isEmpty()) + throw new IllegalStateException("Notebook page movement regression"); + if (folder && menu.getSlot(capacity).mayPlace(page)) + throw new IllegalStateException("Folder exceeded 27 positions"); + menu.clicked(NotebookMenu.NOTEBOOK_SLOTS + 27, 0, ContainerInput.PICKUP, player); + if (player.getItemInHand(hand) != collection || !menu.getCarried().isEmpty()) + throw new IllegalStateException("Notebook active item was movable"); + var ops = RegistryOps.create(com.mojang.serialization.JsonOps.INSTANCE, level.registryAccess()); + var encoded = NotebookContents.CODEC.encodeStart(ops, contents(collection)).getOrThrow(); + var decoded = NotebookContents.CODEC.parse(ops, encoded).getOrThrow(); + var buffer = new RegistryFriendlyByteBuf(io.netty.buffer.Unpooled.buffer(), level.registryAccess()); + try { + NotebookContents.STREAM_CODEC.encode(buffer, decoded); + decoded = NotebookContents.STREAM_CODEC.decode(buffer); + if (decoded.pageCount() != 1 || decoded.pages().get(capacity - 2).isEmpty()) + throw new IllegalStateException("Notebook save/network codec regression"); + } finally { buffer.release(); } + chest.setItem(output++, collection.copy()); + player.setItemInHand(hand, ItemStack.EMPTY); + } + } finally { player.getInventory().clearContent(); } + verify(level); + } + + private static NotebookContents contents(ItemStack stack) { + return stack.getOrDefault(Rivencraft.NOTEBOOK_CONTENTS.get(), NotebookContents.EMPTY); + } + + static void verify(ServerLevel level) { + if (!(level.getBlockEntity(CHEST) instanceof Container chest)) throw new IllegalStateException("Fixture chest missing"); + for (int i = 0; i < 4; i++) { + var collection = chest.getItem(i); + int capacity = i < 2 ? 252 : 27; + if (CollationFolderItem.capacity(collection) != capacity || contents(collection).pageCount() != 1 + || contents(collection).pages().get(capacity - 2).isEmpty()) + throw new IllegalStateException("Notebook chest persistence regression: " + i); + } + } +} diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortReturnLinks.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortReturnLinks.java new file mode 100644 index 0000000..8489105 --- /dev/null +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortReturnLinks.java @@ -0,0 +1,44 @@ +package net.dnorberg.rivencraft.age; + +import net.dnorberg.rivencraft.PortConfig; +import net.dnorberg.rivencraft.Rivencraft; +import net.dnorberg.rivencraft.item.LinkBookTarget; +import net.minecraft.core.component.DataComponents; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.Identifier; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.Prediction; +import net.minecraft.world.item.ItemStack; + +/** Called only after successful Descriptive Book travel and its Disarm effect. */ +public final class PortReturnLinks { + private PortReturnLinks() {} + + public static void afterSuccessfulTravel(ServerPlayer player, Identifier age, LinkBookTarget departure) { + var mode = PortConfig.RETURN_LINK_MODE.get(); + if (mode == PortConfig.ReturnLinkMode.DISABLED) return; + var storage = player.level().getServer().overworld().getDataStorage(); + boolean issue = shouldIssue(mode, storage.computeIfAbsent(PortAgeVisits.TYPE), age); + if (!issue) return; + ItemStack book = createBook(departure); + if (!player.getInventory().add(book)) player.drop(book, false, Prediction.SERVER_ONLY); + // Persist the once-per-Age marker before the next arrival can issue another. + if (mode == PortConfig.ReturnLinkMode.ON_CREATION) + storage.saveAndJoin(); + } + + static boolean shouldIssue(PortConfig.ReturnLinkMode mode, PortAgeVisits visits, Identifier age) { + return switch (mode) { + case DISABLED -> false; + case ALWAYS -> true; + case ON_CREATION -> visits.markFirstVisit(age); + }; + } + + static ItemStack createBook(LinkBookTarget departure) { + ItemStack book = new ItemStack(Rivencraft.LINKING_BOOK.get()); + book.set(Rivencraft.LINK_BOOK_TARGET.get(), departure); + book.set(DataComponents.CUSTOM_NAME, Component.translatable("item.rivencraft.linking_book.return")); + return book; + } +} diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortWritingDeskProbe.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortWritingDeskProbe.java new file mode 100644 index 0000000..bbd0028 --- /dev/null +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortWritingDeskProbe.java @@ -0,0 +1,98 @@ +package net.dnorberg.rivencraft.age; + +import java.util.List; +import net.dnorberg.rivencraft.Rivencraft; +import net.dnorberg.rivencraft.block.WritingDeskBlock; +import net.dnorberg.rivencraft.block.entity.WritingDeskBlockEntity; +import net.dnorberg.rivencraft.item.PageItem; +import net.dnorberg.rivencraft.menu.WritingDeskMenu; +import net.dnorberg.rivencraft.writing.NotebookContents; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.resources.Identifier; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.inventory.ContainerInput; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; + +/** Disposable server fixture for copying transactions, page moves and two-part removal. */ +final class PortWritingDeskProbe { + private static final BlockPos POS = new BlockPos(0, 64, 0); + private PortWritingDeskProbe() {} + private static ItemStack page(String path) { return PageItem.createSymbolPage(Identifier.fromNamespaceAndPath("rivencraft", path)); } + + static void create(ServerLevel level) { + PortAgeTravel.findLanding(level); + var player = net.neoforged.neoforge.common.util.FakePlayerFactory.get(level, + new com.mojang.authlib.GameProfile(java.util.UUID.fromString("f92ad9ee-1ff0-4fe2-bcd3-994d4c035933"), "RivenDeskProbe")); + player.setPos(0.5, 64, 2.5); + // New Minecraft drops Container block-entity contents during removal. + // Removing either half must remove its partner and drop contents once. + for (Direction facing : Direction.Plane.HORIZONTAL) for (boolean extension : List.of(false, true)) { + var state = Rivencraft.WRITING_DESK.get().defaultBlockState().setValue(WritingDeskBlock.FACING, facing); + level.setBlock(POS, state, 3); + Rivencraft.WRITING_DESK.get().setPlacedBy(level, POS, state, player, new ItemStack(Rivencraft.WRITING_DESK_ITEM.get())); + BlockPos other = POS.relative(facing.getClockWise()); + if (!level.getBlockState(other).getValue(WritingDeskBlock.EXTENSION)) + throw new IllegalStateException("Desk extension not placed"); + var desk = (WritingDeskBlockEntity) level.getBlockEntity(POS); + desk.setItem(WritingDeskBlockEntity.PAPER, new ItemStack(Items.PAPER, 3)); + level.destroyBlock(extension ? other : POS, true); + if (level.getBlockState(POS).is(Rivencraft.WRITING_DESK.get()) || level.getBlockState(other).is(Rivencraft.WRITING_DESK.get())) + throw new IllegalStateException("Desk partner survived removal"); + var drops = level.getEntitiesOfClass(net.minecraft.world.entity.item.ItemEntity.class, + new net.minecraft.world.phys.AABB(-3, 60, -3, 4, 69, 4)); + int paper = drops.stream().filter(e -> e.getItem().is(Items.PAPER)).mapToInt(e -> e.getItem().getCount()).sum(); + int desks = drops.stream().filter(e -> e.getItem().is(Rivencraft.WRITING_DESK_ITEM.get())).mapToInt(e -> e.getItem().getCount()).sum(); + if (paper != 3 || desks != 1) throw new IllegalStateException("Desk removal duplicated/lost drops: " + paper + "/" + desks); + drops.forEach(net.minecraft.world.entity.Entity::discard); + } + var state = Rivencraft.WRITING_DESK.get().defaultBlockState(); + level.setBlock(POS, state, 3); + Rivencraft.WRITING_DESK.get().setPlacedBy(level, POS, state, player, new ItemStack(Rivencraft.WRITING_DESK_ITEM.get())); + var desk = (WritingDeskBlockEntity) level.getBlockEntity(POS); + try { + if (desk.canPlaceItem(0, PageItem.createLinkPanelPage())) throw new IllegalStateException("Desk accepted Link Panel"); + desk.setItem(0, page("terrain/flat")); + var folder = new ItemStack(Rivencraft.COLLATION_FOLDER.get()); + folder.set(Rivencraft.NOTEBOOK_CONTENTS.get(), new NotebookContents(List.of(page("weather/off")))); + desk.setItem(1, folder); + desk.setItem(WritingDeskBlockEntity.PAPER, new ItemStack(Items.PAPER, 2)); + desk.setItem(WritingDeskBlockEntity.INK, new ItemStack(Rivencraft.INK_VIAL.get())); + if (desk.inkRemaining() != 16 || !desk.getItem(WritingDeskBlockEntity.RETURN).is(Items.GLASS_BOTTLE)) + throw new IllegalStateException("Desk ink exchange regression"); + var menu = new WritingDeskMenu(1, player.getInventory(), desk); + menu.broadcastChanges(); + if (menu.getSlot(WritingDeskMenu.COPY_RESULT).getItem().isEmpty() || desk.inkRemaining() != 16) + throw new IllegalStateException("Desk virtual result consumed inputs"); + menu.clicked(WritingDeskMenu.COPY_RESULT, 0, ContainerInput.PICKUP, player); + if (menu.getCarried().isEmpty() || desk.inkRemaining() != 15 + || desk.getItem(WritingDeskBlockEntity.PAPER).getCount() != 1 || desk.getItem(0).isEmpty()) + throw new IllegalStateException("Desk copy did not consume exactly one paper/ink"); + menu.setCarried(ItemStack.EMPTY); + for (int slot = 0; slot < player.getInventory().getContainerSize(); slot++) + player.getInventory().setItem(slot, new ItemStack(Items.COBBLESTONE, 64)); + if (!menu.quickMoveStack(player, WritingDeskMenu.COPY_RESULT).isEmpty() || desk.inkRemaining() != 15) + throw new IllegalStateException("Full inventory consumed copy inputs"); + player.getInventory().clearContent(); + if (!menu.clickMenuButton(player, WritingDeskMenu.movePageButtonId(1, 0, 1, 26))) + throw new IllegalStateException("Desk sparse page movement failed"); + if (!menu.clickMenuButton(player, WritingDeskMenu.extractPageButtonId(1, 26, -1)) || menu.getCarried().isEmpty()) + throw new IllegalStateException("Desk page extraction failed"); + menu.setCarried(ItemStack.EMPTY); + for (int i = 0; i < 21; i++) menu.clickMenuButton(player, WritingDeskMenu.NEXT_TABS); + if (menu.firstVisibleTab() != 84 || !menu.clickMenuButton(player, 87)) throw new IllegalStateException("88 desk tabs not reachable"); + desk.setItem(87, page("lighting/bright")); + menu.clickMenuButton(player, WritingDeskMenu.PAGE_BUTTON_BASE); + menu.broadcastChanges(); + } finally { player.getInventory().clearContent(); } + verify(level); + } + + static void verify(ServerLevel level) { + if (!(level.getBlockEntity(POS) instanceof WritingDeskBlockEntity desk) + || desk.activeCollection() != 87 || desk.inkRemaining() != 15 + || desk.getItem(WritingDeskBlockEntity.PAPER).getCount() != 1 || desk.getItem(87).isEmpty() + || desk.displayPage().isEmpty()) throw new IllegalStateException("Desk persistence regression"); + } +} diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/block/CloudPlatformBlock.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/block/CloudPlatformBlock.java new file mode 100644 index 0000000..60cd744 --- /dev/null +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/block/CloudPlatformBlock.java @@ -0,0 +1,17 @@ +package net.dnorberg.rivencraft.block; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; + +/** Solid crystal-textured arrival footing that cannot be collected. */ +public final class CloudPlatformBlock extends Block { + public CloudPlatformBlock(Properties properties) { super(properties); } + + @Override + protected ItemStack getCloneItemStack(LevelReader level, BlockPos pos, BlockState state, boolean includeData) { + return ItemStack.EMPTY; + } +} diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/block/WritingDeskBlock.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/block/WritingDeskBlock.java new file mode 100644 index 0000000..41b0c68 --- /dev/null +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/block/WritingDeskBlock.java @@ -0,0 +1,83 @@ +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.core.Direction; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Containers; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.SimpleMenuProvider; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.BaseEntityBlock; +import net.minecraft.world.level.block.RenderShape; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.block.state.properties.EnumProperty; +import net.minecraft.world.level.block.state.properties.BooleanProperty; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.entity.LivingEntity; +import org.jetbrains.annotations.Nullable; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.phys.BlockHitResult; + +public final class WritingDeskBlock extends BaseEntityBlock { + public static final EnumProperty FACING = BlockStateProperties.HORIZONTAL_FACING; + public static final BooleanProperty EXTENSION = BooleanProperty.create("extension"); + + public WritingDeskBlock(Properties properties) { + super(properties); + registerDefaultState(stateDefinition.any().setValue(FACING, Direction.NORTH).setValue(EXTENSION, false)); + } + + @Override protected void createBlockStateDefinition(StateDefinition.Builder builder) { + builder.add(FACING, EXTENSION); + } + @Override public BlockState getStateForPlacement(BlockPlaceContext context) { + Direction facing = context.getHorizontalDirection().getOpposite(); + BlockPos extension = context.getClickedPos().relative(facing.getClockWise()); + return context.getLevel().getBlockState(extension).canBeReplaced(context) + ? defaultBlockState().setValue(FACING, facing) : null; + } + @Override public void setPlacedBy(Level level, BlockPos pos, BlockState state, + @Nullable LivingEntity placer, ItemStack stack) { + super.setPlacedBy(level, pos, state, placer, stack); + level.setBlock(pos.relative(state.getValue(FACING).getClockWise()), + state.setValue(EXTENSION, true), 3); + } + @Override protected RenderShape getRenderShape(BlockState state) { return RenderShape.MODEL; } + @Override public BlockEntity newBlockEntity(BlockPos pos, BlockState state) { + return state.getValue(EXTENSION) ? null : new WritingDeskBlockEntity(pos, state); + } + @Override protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, + Player player, BlockHitResult hit) { + BlockPos controller = state.getValue(EXTENSION) + ? pos.relative(state.getValue(FACING).getCounterClockWise()) : pos; + if (!level.isClientSide() && player instanceof ServerPlayer serverPlayer + && level.getBlockEntity(controller) instanceof WritingDeskBlockEntity desk) { + serverPlayer.openMenu(new SimpleMenuProvider( + (id, inventory, ignored) -> new WritingDeskMenu(id, inventory, desk), + Component.translatable("container.rivencraft.writing_desk")), + buffer -> { + buffer.writeBlockPos(controller); + WritingDeskCatalog.write(buffer, WritingDeskCatalog.available(desk)); + }); + } + return InteractionResult.SUCCESS; + } + @Override protected void affectNeighborsAfterRemoval(BlockState state, net.minecraft.server.level.ServerLevel level, + BlockPos pos, boolean movedByPiston) { + BlockPos other = pos.relative(state.getValue(FACING).getClockWise(), state.getValue(EXTENSION) ? -1 : 1); + BlockState otherState = level.getBlockState(other); + if (otherState.is(this) && otherState.getValue(EXTENSION) != state.getValue(EXTENSION) + && otherState.getValue(FACING) == state.getValue(FACING)) + level.setBlock(other, net.minecraft.world.level.block.Blocks.AIR.defaultBlockState(), 35); + Containers.updateNeighboursAfterDestroy(state, level, pos); + } +} diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/block/entity/PortBookBinderBlockEntity.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/block/entity/PortBookBinderBlockEntity.java index 1ecc950..8791091 100644 --- a/ports/mc263/src/main/java/net/dnorberg/rivencraft/block/entity/PortBookBinderBlockEntity.java +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/block/entity/PortBookBinderBlockEntity.java @@ -112,6 +112,36 @@ public final class PortBookBinderBlockEntity extends BlockEntity implements Cont return true; } + /** Validate the complete collection before moving any page, as in 1.21.1. */ + public ItemStack importCollection(ItemStack collection, int index) { + if (!net.dnorberg.rivencraft.item.CollationFolderItem.isCollection(collection) + || collection.getCount() != 1) return ItemStack.EMPTY; + var contents = collection.getOrDefault(Rivencraft.NOTEBOOK_CONTENTS.get(), + net.dnorberg.rivencraft.writing.NotebookContents.EMPTY); + int end = nextPageIndex(); + if (index < 0 || index > end) return ItemStack.EMPTY; + ItemStack panel = ItemStack.EMPTY; + List writing = new ArrayList<>(); + for (ItemStack page : contents.pages()) { + if (page.isEmpty()) continue; + if (!page.is(Rivencraft.PAGE.get()) || page.getCount() != 1) return ItemStack.EMPTY; + if (PageItem.isLinkPanel(page)) { + if (!panel.isEmpty() || !items.get(PANEL).isEmpty()) return ItemStack.EMPTY; + panel = page; + } else if (validSymbolPage(page)) writing.add(page); + else return ItemStack.EMPTY; + } + if (end + writing.size() > PAGE_COUNT || writing.isEmpty() && panel.isEmpty()) return ItemStack.EMPTY; + if (!panel.isEmpty()) items.set(PANEL, panel.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()); + ItemStack emptied = collection.copy(); + emptied.set(Rivencraft.NOTEBOOK_CONTENTS.get(), net.dnorberg.rivencraft.writing.NotebookContents.EMPTY); + setChanged(); + return emptied; + } + public ItemStack takePage(int index) { int end = nextPageIndex(); if (index < 0 || index >= end) return ItemStack.EMPTY; diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/block/entity/WritingDeskBlockEntity.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/block/entity/WritingDeskBlockEntity.java new file mode 100644 index 0000000..6c948e8 --- /dev/null +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/block/entity/WritingDeskBlockEntity.java @@ -0,0 +1,173 @@ +package net.dnorberg.rivencraft.block.entity; + +import net.dnorberg.rivencraft.Rivencraft; +import net.dnorberg.rivencraft.item.InkVialItem; +import net.dnorberg.rivencraft.item.PageItem; +import net.dnorberg.rivencraft.item.CollationFolderItem; +import net.dnorberg.rivencraft.writing.InkVialExchange; +import net.dnorberg.rivencraft.writing.PageContent; +import net.dnorberg.rivencraft.writing.NotebookContents; +import net.minecraft.core.BlockPos; +import net.minecraft.core.HolderLookup; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; +import net.minecraft.core.NonNullList; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientGamePacketListener; +import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; +import net.minecraft.world.Container; +import net.minecraft.world.ContainerHelper; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; + +/** Collection tabs, paper, ink vials and empty-vial return are owned by the block. */ +public final class WritingDeskBlockEntity extends BlockEntity implements Container { + public static final int COLLECTIONS = 88; + public static final int INK = COLLECTIONS; + public static final int RETURN = INK + 1; + public static final int PAPER = INK + 2; + /** Unused by new desks; retained so old saves cannot silently lose slot contents. */ + public static final int LEGACY_OUTPUT = INK + 3; + public static final int SIZE = INK + 4; + private NonNullList items = NonNullList.withSize(SIZE, ItemStack.EMPTY); + private int activeCollection; + private int inkRemaining; + private ItemStack displayPage = ItemStack.EMPTY; + + public WritingDeskBlockEntity(BlockPos pos, BlockState state) { + super(Rivencraft.WRITING_DESK_BLOCK_ENTITY.get(), pos, state); + } + + public int activeCollection() { return activeCollection; } + public int inkRemaining() { return inkRemaining; } + public ItemStack displayPage() { return displayPage; } + public void setDisplayPage(ItemStack page) { + ItemStack next = page.isEmpty() ? ItemStack.EMPTY : page.copyWithCount(1); + if (!ItemStack.matches(displayPage, next)) { + displayPage = next; + setChanged(); + } + } + public void setActiveCollection(int index) { + if (index >= 0 && index < COLLECTIONS && activeCollection != index) { + activeCollection = index; + setChanged(); + } + } + + @Override public int getContainerSize() { return SIZE; } + @Override public boolean isEmpty() { return items.stream().allMatch(ItemStack::isEmpty); } + @Override public ItemStack getItem(int slot) { return items.get(slot); } + @Override public ItemStack removeItem(int slot, int amount) { + ItemStack removed = ContainerHelper.removeItem(items, slot, amount); + if (!removed.isEmpty()) { + if (slot < COLLECTIONS) displayPage = ItemStack.EMPTY; + setChanged(); + if (slot == RETURN) refillEmptyInkBar(); + } + return removed; + } + @Override public ItemStack removeItemNoUpdate(int slot) { return ContainerHelper.takeItem(items, slot); } + @Override public void setItem(int slot, ItemStack stack) { + items.set(slot, stack); + if (stack.getCount() > getMaxStackSize()) stack.setCount(getMaxStackSize()); + if (slot < COLLECTIONS) displayPage = ItemStack.EMPTY; + setChanged(); + if (slot == INK || slot == RETURN) refillEmptyInkBar(); + } + @Override public boolean stillValid(Player player) { + return level != null && level.isLoaded(worldPosition) + && level.getBlockEntity(worldPosition) == this + && player.distanceToSqr(worldPosition.getX() + 0.5, worldPosition.getY() + 0.5, + worldPosition.getZ() + 0.5) <= 64.0; + } + @Override public boolean canPlaceItem(int slot, ItemStack stack) { + if (slot < COLLECTIONS) return CollationFolderItem.isCollection(stack) + && stack.getOrDefault(Rivencraft.NOTEBOOK_CONTENTS.get(), NotebookContents.EMPTY) + .pages().stream().noneMatch(page -> PageItem.content(page).kind() == PageContent.Kind.LINK_PANEL) + || stack.is(Rivencraft.PAGE.get()) + && PageItem.content(stack).kind() != PageContent.Kind.LINK_PANEL; + return switch (slot) { + case INK -> stack.is(Rivencraft.INK_VIAL.get()); + case RETURN -> false; + case PAPER -> stack.is(Items.PAPER); + case LEGACY_OUTPUT -> false; + default -> false; + }; + } + + /** Consume one vial only when the empty meter can return its glass bottle. */ + private void refillEmptyInkBar() { + 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. */ + public boolean canConsumeCopyMaterials() { + ItemStack paper = items.get(PAPER); + if (!canPlaceItem(PAPER, paper) || paper.isEmpty()) return false; + int cost = net.dnorberg.rivencraft.PortConfig.SYMBOL_INK_COST.get(); + return InkVialExchange.canPay(items, INK, RETURN, inkRemaining, cost); + } + + public boolean consumeCopyMaterials() { + if (!canConsumeCopyMaterials()) return false; + int cost = net.dnorberg.rivencraft.PortConfig.SYMBOL_INK_COST.get(); + if (inkRemaining < cost) { + inkRemaining += InkVialExchange.exchange(items, INK, RETURN); + } + inkRemaining -= cost; + removeItem(PAPER, 1); + setChanged(); + if (inkRemaining == 0) refillEmptyInkBar(); + return true; + } + @Override public void setLevel(Level level) { + super.setLevel(level); + refillEmptyInkBar(); + } + @Override public void clearContent() { + items = NonNullList.withSize(SIZE, ItemStack.EMPTY); + inkRemaining = 0; + displayPage = ItemStack.EMPTY; + setChanged(); + } + @Override protected void loadAdditional(ValueInput input) { + super.loadAdditional(input); + items = NonNullList.withSize(SIZE, ItemStack.EMPTY); + ContainerHelper.loadAllItems(input, items); + activeCollection = Math.clamp(input.getIntOr("ActiveCollection", 0), 0, COLLECTIONS - 1); + inkRemaining = Math.clamp(input.getIntOr("InkRemaining", 0), 0, InkVialItem.CAPACITY); + displayPage = input.read("DisplayPage", ItemStack.CODEC).orElse(ItemStack.EMPTY); + refillEmptyInkBar(); + } + @Override protected void saveAdditional(ValueOutput output) { + super.saveAdditional(output); + ContainerHelper.saveAllItems(output, items); + output.putInt("ActiveCollection", activeCollection); + output.putInt("InkRemaining", inkRemaining); + if (!displayPage.isEmpty()) output.store("DisplayPage", ItemStack.CODEC, displayPage); + } + @Override public Packet getUpdatePacket() { + return ClientboundBlockEntityDataPacket.create(this); + } + @Override public CompoundTag getUpdateTag(HolderLookup.Provider registries) { + return saveWithoutMetadata(registries); + } + @Override public void setChanged() { + super.setChanged(); + if (level != null && !level.isClientSide()) { + level.sendBlockUpdated(worldPosition, getBlockState(), getBlockState(), 2); + } + } +} diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/client/AgeCatalogClient.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/client/AgeCatalogClient.java index b2e4d11..19e85ef 100644 --- a/ports/mc263/src/main/java/net/dnorberg/rivencraft/client/AgeCatalogClient.java +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/client/AgeCatalogClient.java @@ -26,6 +26,7 @@ public final class AgeCatalogClient { @SubscribeEvent public static void register(RegisterClientPayloadHandlersEvent event) { + event.register(net.dnorberg.rivencraft.network.WritingDeskCatalogPayload.TYPE, ClientWritingDeskCatalog::handle); event.register(AgeCatalogPayload.TYPE, (payload, context) -> { if (!(context.listener() instanceof ClientPacketListener listener)) return; net.dnorberg.rivencraft.age.PortAgeColors.updateClient(payload.colors()); diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/client/ClientWritingDeskCatalog.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/client/ClientWritingDeskCatalog.java new file mode 100644 index 0000000..ba82635 --- /dev/null +++ b/ports/mc263/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/ports/mc263/src/main/java/net/dnorberg/rivencraft/client/DroppedBookRenderer.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/client/DroppedBookRenderer.java new file mode 100644 index 0000000..32f1c5c --- /dev/null +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/client/DroppedBookRenderer.java @@ -0,0 +1,57 @@ +package net.dnorberg.rivencraft.client; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.math.Axis; +import net.dnorberg.rivencraft.Rivencraft; +import net.dnorberg.rivencraft.entity.DroppedBookEntity; +import net.minecraft.client.model.geom.ModelLayers; +import net.minecraft.client.model.object.book.BookModel; +import net.minecraft.client.renderer.SubmitNodeCollector; +import net.minecraft.client.renderer.entity.EntityRenderer; +import net.minecraft.client.renderer.entity.EntityRendererProvider; +import net.minecraft.client.renderer.entity.state.EntityRenderState; +import net.minecraft.client.renderer.state.level.CameraRenderState; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.resources.Identifier; + +/** Uses the same skyward-facing open book and textures as the 1.21.1 renderer. */ +public final class DroppedBookRenderer extends EntityRenderer { + private static final Identifier LINKING = Identifier.fromNamespaceAndPath("rivencraft", "textures/entity/linking_book.png"); + private static final Identifier DESCRIPTIVE = Identifier.fromNamespaceAndPath("rivencraft", "textures/entity/descriptive_book.png"); + private final BookModel model; + + public DroppedBookRenderer(EntityRendererProvider.Context context) { + super(context); + model = new BookModel(context.bakeLayer(ModelLayers.BOOK)); + shadowRadius = 0.25F; + } + + public static final class State extends EntityRenderState { + float yaw; + boolean descriptive; + } + + @Override + public State createRenderState() { return new State(); } + + @Override + public void extractRenderState(DroppedBookEntity entity, State state, float partialTick) { + super.extractRenderState(entity, state, partialTick); + state.yaw = entity.getYRot(); + state.descriptive = entity.getItem().is(Rivencraft.DESCRIPTIVE_BOOK.get()); + } + + @Override + public void submit(State state, PoseStack pose, SubmitNodeCollector collector, CameraRenderState camera) { + pose.pushPose(); + pose.translate(0.0F, 0.0625F, 0.0F); + pose.rotateDegrees(Axis.YP, state.yaw + 90.0F); + pose.rotateDegrees(Axis.ZP, 90.0F); + pose.scale(0.8F, 0.8F, 0.8F); + collector.submitModel(model, BookModel.State.forAnimation(0, 0, 0, 1.2F), pose, + state.descriptive ? DESCRIPTIVE : LINKING, state.lightCoords, OverlayTexture.NO_OVERLAY, + state.outlineColor); + pose.popPose(); + super.submit(state, pose, collector, camera); + } +} diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/client/NotebookScreen.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/client/NotebookScreen.java new file mode 100644 index 0000000..a67be90 --- /dev/null +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/client/NotebookScreen.java @@ -0,0 +1,317 @@ +package net.dnorberg.rivencraft.client; + +import com.mojang.blaze3d.platform.InputConstants; +import java.util.Locale; +import net.dnorberg.rivencraft.Rivencraft; +import net.dnorberg.rivencraft.item.PageItem; +import net.dnorberg.rivencraft.menu.NotebookMenu; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.input.MouseButtonEvent; +import net.minecraft.client.input.KeyEvent; +import net.minecraft.client.renderer.RenderPipelines; +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.Identifier; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.inventory.ContainerInput; +import net.minecraft.world.item.ItemStack; + +/** A page surface modelled on Legacy's folder GUI, backed by the Notebook menu. */ +public final class NotebookScreen extends AbstractContainerScreen { + private static final int SURFACE_TOP = 20; + private static final int SURFACE_BOTTOM = 132; + private static final int SURFACE_RIGHT = 156; + private static final int PAGE_WIDTH = 30; + private static final int PAGE_HEIGHT = 40; + private static final int PAGE_STEP_X = 31; + private static final int PAGE_STEP_Y = 41; + private static final int PAGE_COLUMNS = 5; + private static final Identifier PAGE_ATLAS = texture("gui/notebook_pages"); + private EditBox textInput; + private boolean draggingScrollbar; + private boolean surfaceClickPendingRelease; + private int selectedSlot = -1; + private int scroll; + + public NotebookScreen(NotebookMenu menu, Inventory inventory, Component title) { + super(menu, inventory, title, 176, 213); + inventoryLabelY = 123; + } + + @Override public boolean isPauseScreen() { return false; } + + @Override + protected void init() { + super.init(); + Button sortButton = Button.builder(Component.literal("AZ"), button -> {}) + .bounds(leftPos, topPos, 18, 18).build(); + Button allButton = Button.builder(Component.literal("ALL"), button -> {}) + .bounds(leftPos + 20, topPos, 26, 18).build(); + sortButton.active = false; + allButton.active = false; + addRenderableWidget(sortButton); + addRenderableWidget(allButton); + textInput = new EditBox(font, leftPos + 48, topPos, 128, 18, + Component.translatable("screen.rivencraft.notebook.search")); + textInput.setMaxLength(32); + textInput.setHint(Component.translatable("screen.rivencraft.notebook.search")); + textInput.setResponder(value -> scroll = 0); + addRenderableWidget(textInput); + } + + @Override + public void extractBackground(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float partialTick) { + super.extractBackground(graphics, mouseX, mouseY, partialTick); + graphics.fill(leftPos, topPos + SURFACE_TOP, leftPos + SURFACE_RIGHT, + topPos + SURFACE_BOTTOM, 0xB0000000); + drawBeveledPanel(graphics, leftPos, topPos + 132, imageWidth, 81); + for (int row = 0; row < 3; row++) { + for (int column = 0; column < 9; column++) drawSlot(graphics, 7 + column * 18, 134 + row * 18); + } + for (int column = 0; column < 9; column++) drawSlot(graphics, 7 + column * 18, 192); + } + + @Override + public void extractContents(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float partialTick) { + super.extractContents(graphics, mouseX, mouseY, partialTick); + int pageSlots = displayedPageSlots(); + scroll = Math.min(scroll, maxScroll(pageSlots)); + graphics.nextStratum(); + graphics.enableScissor(leftPos, topPos + SURFACE_TOP, leftPos + SURFACE_RIGHT, topPos + SURFACE_BOTTOM); + for (int index = 0; index < pageSlots; index++) { + int x = leftPos + (index % PAGE_COLUMNS) * PAGE_STEP_X; + int y = topPos + SURFACE_TOP + (index / PAGE_COLUMNS) * PAGE_STEP_Y - scroll; + if (y + PAGE_HEIGHT > topPos + SURFACE_TOP && y < topPos + SURFACE_BOTTOM) drawPage(graphics, index, x, y); + } + graphics.disableScissor(); + drawScrollbar(graphics, pageSlots); + } + + @Override + protected void extractTooltip(GuiGraphicsExtractor graphics, int mouseX, int mouseY) { + int hovered = pageAt(mouseX, mouseY); + if (hovered >= 0 && menu.getCarried().isEmpty() && !menu.getSlot(hovered).getItem().isEmpty()) { + ItemStack page = menu.getSlot(hovered).getItem(); + if (matchesSearch(page)) graphics.setTooltipForNextFrame(font, page, mouseX, mouseY); + } else super.extractTooltip(graphics, mouseX, mouseY); + } + + @Override + protected void extractSlots(GuiGraphicsExtractor graphics, int mouseX, int mouseY) { + // Collection slots are drawn above as large pages. Do not submit up to + // 252 additional item models at their off-screen server-slot positions. + for (int index = NotebookMenu.NOTEBOOK_SLOTS; index < menu.slots.size(); index++) { + var slot = menu.getSlot(index); + if (slot.isActive()) extractSlot(graphics, slot, mouseX, mouseY); + } + } + + @Override + protected void extractLabels(GuiGraphicsExtractor graphics, int mouseX, int mouseY) { + graphics.text(font, playerInventoryTitle, inventoryLabelX, inventoryLabelY, 0xFF404040, false); + } + + @Override + public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) { + double mouseX = event.x(), mouseY = event.y(); + int button = event.button(); + if (inScrollbar(mouseX, mouseY) && button == InputConstants.MOUSE_BUTTON_LEFT) { + draggingScrollbar = true; + setScrollFromMouse(mouseY, displayedPageSlots()); + return true; + } + int index = pageAt(mouseX, mouseY); + if (index >= 0 && (button == InputConstants.MOUSE_BUTTON_LEFT || button == InputConstants.MOUSE_BUTTON_RIGHT)) { + ItemStack page = menu.getSlot(index).getItem(); + if ((!page.isEmpty() && !matchesSearch(page)) + || (page.isEmpty() && (!menu.getCarried().is(Rivencraft.PAGE.get()) + || PageItem.isLinkPanel(menu.getCarried())))) { + return true; + } + setFocused(null); + textInput.setFocused(false); + selectedSlot = index; + slotClicked(menu.getSlot(index), index, button == InputConstants.MOUSE_BUTTON_RIGHT ? 1 : 0, ContainerInput.PICKUP); + surfaceClickPendingRelease = true; + return true; + } + return super.mouseClicked(event, doubleClick); + } + + @Override + public boolean mouseDragged(MouseButtonEvent event, double dragX, double dragY) { + double mouseY = event.y(); + int button = event.button(); + if (draggingScrollbar && button == InputConstants.MOUSE_BUTTON_LEFT) { + setScrollFromMouse(mouseY, displayedPageSlots()); + return true; + } + return super.mouseDragged(event, dragX, dragY); + } + + @Override + public boolean mouseReleased(MouseButtonEvent event) { + if (draggingScrollbar || surfaceClickPendingRelease) { + draggingScrollbar = false; + surfaceClickPendingRelease = false; + return true; + } + draggingScrollbar = false; + return super.mouseReleased(event); + } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) { + if (inSurface(mouseX, mouseY) || inScrollbar(mouseX, mouseY)) { + scroll = Math.clamp(scroll - (int)Math.signum(scrollY) * 20, + 0, maxScroll(displayedPageSlots())); + return true; + } + return super.mouseScrolled(mouseX, mouseY, scrollX, scrollY); + } + + @Override + public boolean keyPressed(KeyEvent event) { + // Let the container close normally, including returning its carried + // stack, even while the search field owns keyboard focus. + if (event.isEscape()) return super.keyPressed(event); + if (textInput != null && textInput.isFocused()) { + textInput.keyPressed(event); + // EditBox does not consume ordinary letter key presses; the matching charTyped + // event handles text input. Do not pass them to the container's inventory keys. + return true; + } + if (selectedSlot >= 0 && (event.isLeft() || event.isRight())) { + moveSelected(event.isRight()); + return true; + } + return super.keyPressed(event); + } + + private int displayedPageSlots() { + if (menu.getCarried().is(Rivencraft.PAGE.get())) { + return menu.pageCapacity(); + } + for (int index = menu.pageCapacity() - 1; index >= 0; index--) { + if (!menu.getSlot(index).getItem().isEmpty()) { + return index + 1; + } + } + return 0; + } + + private boolean matchesSearch(ItemStack page) { + return textInput.getValue().isBlank() + || page.getHoverName().getString().toLowerCase(Locale.ROOT) + .contains(textInput.getValue().toLowerCase(Locale.ROOT)); + } + + private int pageAt(double mouseX, double mouseY) { + if (!inSurface(mouseX, mouseY)) return -1; + int localX = (int)mouseX - leftPos; + int localY = (int)mouseY - topPos - SURFACE_TOP + scroll; + int column = localX / PAGE_STEP_X; + int index = (localY / PAGE_STEP_Y) * PAGE_COLUMNS + column; + return column < PAGE_COLUMNS && localX % PAGE_STEP_X < PAGE_WIDTH + && localY % PAGE_STEP_Y < PAGE_HEIGHT && index < displayedPageSlots() + ? index : -1; + } + + private boolean inSurface(double mouseX, double mouseY) { + return mouseX >= leftPos && mouseX < leftPos + SURFACE_RIGHT + && mouseY >= topPos + SURFACE_TOP && mouseY < topPos + SURFACE_BOTTOM; + } + + private boolean inScrollbar(double mouseX, double mouseY) { + return mouseX >= leftPos + SURFACE_RIGHT && mouseX < leftPos + imageWidth + && mouseY >= topPos + SURFACE_TOP && mouseY < topPos + SURFACE_BOTTOM; + } + + private int maxScroll(int pageCount) { + int rows = (pageCount + PAGE_COLUMNS - 1) / PAGE_COLUMNS; + return Math.max(0, rows * PAGE_STEP_Y - (SURFACE_BOTTOM - SURFACE_TOP)); + } + + private void setScrollFromMouse(double mouseY, int pageCount) { + int maximum = maxScroll(pageCount); + double fraction = (mouseY - topPos - SURFACE_TOP - 10.0) + / (SURFACE_BOTTOM - SURFACE_TOP - 20.0); + scroll = Math.clamp((int)(Math.clamp(fraction, 0.0, 1.0) * maximum), 0, maximum); + } + + private void drawScrollbar(GuiGraphicsExtractor graphics, int pageCount) { + int x = leftPos + SURFACE_RIGHT; + int y = topPos + SURFACE_TOP; + int height = SURFACE_BOTTOM - SURFACE_TOP; + graphics.fill(x, y, x + 20, y + height, 0xFF858585); + graphics.outline(x, y, 20, height, 0xFF262626); + for (int line = y + 5; line < y + height - 4; line += 5) { + graphics.fill(x + 4, line, x + 16, line + 1, 0xFFB7B7B7); + } + int maximum = maxScroll(pageCount); + int thumbY = y + 4 + (maximum == 0 ? 0 : scroll * (height - 23) / maximum); + graphics.fill(x + 4, thumbY, x + 16, thumbY + 15, 0xFFC9C9C9); + graphics.outline(x + 4, thumbY, 12, 15, 0xFF414141); + } + + private void drawPage(GuiGraphicsExtractor graphics, int index, int x, int y) { + ItemStack page = menu.getSlot(index).getItem(); + boolean dimmed = page.isEmpty() || !matchesSearch(page); + // Legacy's Notebook draws the atlas' native 30x40 page, not a stretched item icon. + graphics.nextStratum(); + graphics.blit(RenderPipelines.GUI_TEXTURED, PAGE_ATLAS, x, y, 156, 0, PAGE_WIDTH, PAGE_HEIGHT, 256, 256); + if (!page.isEmpty() && page.has(Rivencraft.SYMBOL_PAGE_ID.get())) { + graphics.nextStratum(); + ItemStack glyph = page.copy(); + glyph.set(Rivencraft.PAGE_GLYPH_ONLY.get(), true); + graphics.pose().pushMatrix(); + graphics.pose().translate(x + 1, y + 6); + graphics.pose().scale(1.75F, 1.75F); + graphics.fakeItem(glyph, 0, 0); + graphics.pose().popMatrix(); + graphics.nextStratum(); + } + if (dimmed) { + graphics.fill(x, y, x + PAGE_WIDTH, y + PAGE_HEIGHT, 0xB8000000); + } else if (!net.dnorberg.rivencraft.writing.PortAuthoredWriting.isPageImplemented( + page.get(Rivencraft.SYMBOL_PAGE_ID.get()))) { + graphics.fill(x, y, x + PAGE_WIDTH, y + PAGE_HEIGHT, 0x55FF3333); + } + if (index == selectedSlot && !page.isEmpty()) { + graphics.outline(x, y, PAGE_WIDTH, PAGE_HEIGHT, 0xFFFFD061); + } + } + + private static Identifier texture(String path) { + return Identifier.fromNamespaceAndPath(Rivencraft.MOD_ID, "textures/" + path + ".png"); + } + + private void moveSelected(boolean next) { + int target = selectedSlot + (next ? 1 : -1); + if (selectedSlot < 0 || target < 0 || target >= NotebookMenu.NOTEBOOK_SLOTS + || menu.getSlot(selectedSlot).getItem().isEmpty()) return; + int buttonId = (next ? NotebookMenu.MOVE_NEXT_BASE : NotebookMenu.MOVE_PREVIOUS_BASE) + selectedSlot; + if (menu.clickMenuButton(minecraft.player, buttonId)) { + minecraft.gameMode.handleInventoryButtonClick(menu.containerId, buttonId); + selectedSlot = target; + } + } + + private static void drawBeveledPanel(GuiGraphicsExtractor graphics, int x, int y, int width, int height) { + graphics.fill(x, y, x + width, y + height, 0xFFC6C6C6); + graphics.fill(x, y, x + width, y + 2, 0xFFFFFFFF); + graphics.fill(x, y, x + 2, y + height, 0xFFFFFFFF); + graphics.fill(x, y + height - 2, x + width, y + height, 0xFF555555); + graphics.fill(x + width - 2, y, x + width, y + height, 0xFF555555); + } + + private void drawSlot(GuiGraphicsExtractor graphics, int x, int y) { + graphics.fill(leftPos + x, topPos + y, leftPos + x + 18, topPos + y + 18, 0xFF8B8B8B); + graphics.fill(leftPos + x, topPos + y, leftPos + x + 18, topPos + y + 1, 0xFF373737); + graphics.fill(leftPos + x, topPos + y, leftPos + x + 1, topPos + y + 18, 0xFF373737); + graphics.fill(leftPos + x + 17, topPos + y, leftPos + x + 18, topPos + y + 18, 0xFFFFFFFF); + graphics.fill(leftPos + x, topPos + y + 17, leftPos + x + 18, topPos + y + 18, 0xFFFFFFFF); + } +} diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/client/PortBookBinderClientEvents.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/client/PortBookBinderClientEvents.java index 016e7f1..a7d85dd 100644 --- a/ports/mc263/src/main/java/net/dnorberg/rivencraft/client/PortBookBinderClientEvents.java +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/client/PortBookBinderClientEvents.java @@ -18,12 +18,16 @@ public final class PortBookBinderClientEvents { public static void registerBlockEntityRenderers( net.neoforged.neoforge.client.event.EntityRenderersEvent.RegisterRenderers event) { event.registerBlockEntityRenderer(Rivencraft.STAR_FISSURE_BLOCK_ENTITY.get(), context -> new StarFissureRenderer()); + event.registerEntityRenderer(Rivencraft.DROPPED_BOOK.get(), DroppedBookRenderer::new); + event.registerBlockEntityRenderer(Rivencraft.WRITING_DESK_BLOCK_ENTITY.get(), WritingDeskRenderer::new); } @SubscribeEvent public static void registerScreens(RegisterMenuScreensEvent event) { event.register(Rivencraft.BOOK_BINDER_MENU.get(), PortBookBinderScreen::new); event.register(Rivencraft.INK_MIXER_MENU.get(), InkMixerScreen::new); + event.register(Rivencraft.NOTEBOOK_MENU.get(), NotebookScreen::new); + event.register(Rivencraft.WRITING_DESK_MENU.get(), WritingDeskScreen::new); } @SubscribeEvent diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/client/WritingDeskRenderer.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/client/WritingDeskRenderer.java new file mode 100644 index 0000000..d9ab4ee --- /dev/null +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/client/WritingDeskRenderer.java @@ -0,0 +1,60 @@ +package net.dnorberg.rivencraft.client; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.math.Axis; +import net.dnorberg.rivencraft.block.WritingDeskBlock; +import net.dnorberg.rivencraft.block.entity.WritingDeskBlockEntity; +import net.minecraft.client.renderer.SubmitNodeCollector; +import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; +import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider; +import net.minecraft.client.renderer.blockentity.state.BlockEntityRenderState; +import net.minecraft.client.renderer.feature.ModelFeatureRenderer; +import net.minecraft.client.renderer.item.ItemModelResolver; +import net.minecraft.client.renderer.item.ItemStackRenderState; +import net.minecraft.client.renderer.state.level.CameraRenderState; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.world.item.ItemDisplayContext; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.phys.Vec3; + +/** The existing selected-page and white-paper display, extracted for 26.3. */ +public final class WritingDeskRenderer implements BlockEntityRenderer { + private final ItemModelResolver resolver; + public WritingDeskRenderer(BlockEntityRendererProvider.Context context) { resolver = context.itemModelResolver(); } + public static final class State extends BlockEntityRenderState { + final ItemStackRenderState paper = new ItemStackRenderState(); + final ItemStackRenderState page = new ItemStackRenderState(); + int layers; + float rotation; + } + @Override public State createRenderState() { return new State(); } + @Override public void extractRenderState(WritingDeskBlockEntity desk, State state, float partialTick, + Vec3 camera, ModelFeatureRenderer.CrumblingOverlay overlay) { + BlockEntityRenderer.super.extractRenderState(desk, state, partialTick, camera, overlay); + int count = desk.getItem(WritingDeskBlockEntity.PAPER).getCount(); + state.layers = count == 0 ? 0 : Math.min(4, 1 + count / 16); + state.rotation = 180 - desk.getBlockState().getValue(WritingDeskBlock.FACING).toYRot(); + resolver.updateForTopItem(state.paper, new ItemStack(Items.PAPER), ItemDisplayContext.FIXED, desk.getLevel(), null, 0); + resolver.updateForTopItem(state.page, desk.displayPage(), ItemDisplayContext.FIXED, desk.getLevel(), null, 0); + } + @Override public void submit(State state, PoseStack pose, SubmitNodeCollector collector, CameraRenderState camera) { + pose.pushPose(); + pose.translate(0.5, 0, 0.5); + pose.rotateDegrees(Axis.YP, state.rotation); + for (int i = 0; i < state.layers; i++) draw(state.paper, pose, collector, state.lightCoords, + 1.22, 0.27 + i * 0.008, -0.14, 0.55F); + if (!state.page.isEmpty()) draw(state.page, pose, collector, state.lightCoords, 1.0, 0.572, 0, 0.8F); + pose.popPose(); + } + private static void draw(ItemStackRenderState item, PoseStack pose, SubmitNodeCollector collector, + int light, double x, double y, double z, float scale) { + pose.pushPose(); + pose.translate(x, y, z); + pose.rotateDegrees(Axis.XP, 90); + pose.scale(scale, scale, scale); + item.submit(pose, collector, light, OverlayTexture.NO_OVERLAY, 0); + pose.popPose(); + } + @Override public boolean shouldRenderOffScreen() { return true; } +} diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/client/WritingDeskScreen.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/client/WritingDeskScreen.java new file mode 100644 index 0000000..a18b9db --- /dev/null +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/client/WritingDeskScreen.java @@ -0,0 +1,510 @@ +package net.dnorberg.rivencraft.client; + +import java.util.List; +import java.util.Locale; +import java.util.ArrayList; +import java.util.Comparator; +import net.dnorberg.rivencraft.Rivencraft; +import net.dnorberg.rivencraft.item.PageItem; +import net.dnorberg.rivencraft.writing.PageContent; +import net.dnorberg.rivencraft.menu.WritingDeskMenu; +import net.dnorberg.rivencraft.block.entity.WritingDeskBlockEntity; +import net.dnorberg.rivencraft.item.CollationFolderItem; +import net.dnorberg.rivencraft.writing.NotebookContents; +import net.dnorberg.rivencraft.writing.SymbolDisplayMetadata; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.components.Tooltip; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.Identifier; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import net.minecraft.client.input.MouseButtonEvent; +import net.minecraft.client.input.KeyEvent; +import com.mojang.blaze3d.platform.InputConstants; +import net.minecraft.client.renderer.RenderPipelines; + +/** Legacy desk atlas layout, with four visible tabs out of the desk's 88 collections. */ +public final class WritingDeskScreen extends AbstractContainerScreen { + private static final Identifier PAGE_ATLAS = Identifier.fromNamespaceAndPath( + Rivencraft.MOD_ID, "textures/gui/notebook_pages.png"); + private static final Identifier LEGACY_DESK = Identifier.fromNamespaceAndPath( + Rivencraft.MOD_ID, "textures/gui/writing_desk_legacy.png"); + private static final Identifier SCROLLBAR = Identifier.fromNamespaceAndPath( + Rivencraft.MOD_ID, "textures/gui/scrollbar.png"); + private int lastCollection = -1; + private int selectedPage; + private int scrollRows; + private boolean allMode; + private boolean sortAlphabetically; + private int selectedCollection = -1; + private Identifier selectedCategory; + private EditBox search; + private Button categoryButton; + private Button allButton; + private Button sortButton; + private boolean draggingScrollbar; + private record DisplayPage(int collection, int index, ItemStack stack) {} + private DisplayPage pageDragSource; + private double pageDragStartX; + private double pageDragStartY; + private boolean draggingPage; + + private boolean isPhysicalPageView() { + return !allMode && !sortAlphabetically && selectedCategory == null + && (search == null || search.getValue().isBlank()); + } + + public WritingDeskScreen(WritingDeskMenu menu, Inventory inventory, Component title) { + super(menu, inventory, title, 409, 186); + titleLabelX = 222; + } + + @Override public boolean isPauseScreen() { return false; } + + @Override protected void init() { + super.init(); + sortButton = addRenderableWidget(Button.builder(Component.literal("AZ"), button -> { + sortAlphabetically = !sortAlphabetically; + scrollRows = 0; + }).bounds(leftPos + 58, topPos, 20, 18) + .tooltip(Tooltip.create(Component.translatable("screen.rivencraft.writing_desk.sort"))).build()); + allButton = addRenderableWidget(Button.builder(Component.literal("ALL"), button -> { + allMode = !allMode; + scrollRows = 0; + if (!allMode) { + selectedCollection = -1; + selectedPage = 0; + if (minecraft != null && minecraft.gameMode != null) + minecraft.gameMode.handleInventoryButtonClick(menu.containerId, WritingDeskMenu.PAGE_BUTTON_BASE); + } + }).bounds(leftPos + 80, topPos, 28, 18) + .tooltip(Tooltip.create(Component.translatable("screen.rivencraft.writing_desk.all"))).build()); + search = new EditBox(font, leftPos + 110, topPos, 101, 18, + Component.translatable("screen.rivencraft.writing_desk.search")); + search.setMaxLength(64); + search.setHint(Component.translatable("screen.rivencraft.writing_desk.search")); + search.setResponder(value -> scrollRows = 0); + addRenderableWidget(search); + categoryButton = addRenderableWidget(Button.builder(Component.literal("C"), 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 ? "C" : "C*")); + scrollRows = 0; + }).bounds(leftPos + 213, topPos, 18, 18).build()); + } + + private List filteredPages() { + String query = search == null ? "" : search.getValue().trim().toLowerCase(Locale.ROOT); + List entries = new ArrayList<>(); + int from = allMode ? 0 : menu.activeCollection(); + int until = allMode ? WritingDeskBlockEntity.COLLECTIONS : from + 1; + for (int collection = from; collection < until; collection++) { + List pages = pages(collection); + int last = -1; + for (int i = 0; i < pages.size(); i++) if (!pages.get(i).isEmpty()) last = i; + // A notebook has exactly its normal GUI capacity; a loose page has one position. + if (isPhysicalPageView()) last = menu.pageCapacity(collection) - 1; + for (int i = 0; i <= last; i++) { + ItemStack page = i < pages.size() ? pages.get(i) : ItemStack.EMPTY; + if (page.isEmpty() && (allMode || sortAlphabetically || !query.isEmpty() || selectedCategory != null)) continue; + if (!page.isEmpty() && !query.isEmpty() + && !page.getHoverName().getString().toLowerCase(Locale.ROOT).contains(query) + && !PageItem.content(page).symbol().map(symbol -> symbol.id().toString() + .toLowerCase(Locale.ROOT).contains(query)).orElse(false)) continue; + if (!page.isEmpty() && selectedCategory != null) { + Identifier id = PageItem.content(page).symbol().map(symbol -> symbol.id()).orElse(null); + if (menu.catalog().stream().noneMatch(metadata -> metadata.id().equals(id) + && metadata.category().equals(selectedCategory))) continue; + } + entries.add(new DisplayPage(collection, i, page)); + } + } + if (sortAlphabetically) entries.sort(Comparator + .comparing((DisplayPage entry) -> entry.stack().getHoverName().getString(), String.CASE_INSENSITIVE_ORDER) + .thenComparingInt(DisplayPage::collection).thenComparingInt(DisplayPage::index)); + return entries; + } + + @Override public void extractBackground(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float partialTick) { + int x = leftPos; + int y = topPos; + // Legacy GuiWritingDesk draws the entire 176x166 panel, not a stretched swatch. + graphics.blit(RenderPipelines.GUI_TEXTURED, LEGACY_DESK, x + 233, y + 20, 0, 0, 176, 166, 256, 256); + drawTabControl(graphics, 20, 203, menu.firstVisibleTab() > 0); + for (int i = 0; i < 4; i++) { + int tab = menu.firstVisibleTab() + i; + int tabY = y + 29 + i * 37; + graphics.blit(RenderPipelines.GUI_TEXTURED, LEGACY_DESK, x, tabY, 0, 166, 58, 37, 256, 256, + tab == menu.activeCollection() && !allMode ? 0xFF8080FF : -1); + // Legacy numbers its Writing Desk collections from zero. + drawNumeral(graphics, tab, x + (tab < 25 ? 8 : 4), tabY + 3, 19); + ItemStack collection = menu.getSlot(tab).getItem(); + if (!collection.isEmpty()) { + String name = collection.getHoverName().getString(); + float scale = Math.min(1F, 50F / Math.max(1, font.width(name))); + graphics.pose().pushMatrix(); + graphics.pose().translate(x + 4, tabY + 25); + graphics.pose().scale(scale, scale); + graphics.text(font, name, 0, 0, 0xFF404040, false); + graphics.pose().popMatrix(); + } + } + drawTabControl(graphics, 177, 212, menu.firstVisibleTab() < WritingDeskBlockEntity.COLLECTIONS - 4); + // GuiElementVSlider: 4-pixel caps, repeated 22-pixel track, 12x15 thumb. + graphics.blit(RenderPipelines.GUI_TEXTURED, SCROLLBAR, x + 213, y + 20, 0, 0, 20, 4, 32, 32); + for (int offset = 4; offset < 162; offset += 22) + graphics.blit(RenderPipelines.GUI_TEXTURED, SCROLLBAR, x + 213, y + 20 + offset, 0, 4, 20, Math.min(22, 162 - offset), 32, 32); + graphics.blit(RenderPipelines.GUI_TEXTURED, SCROLLBAR, x + 213, y + 182, 0, 26, 20, 4, 32, 32); + int totalRows = (filteredPages().size() + 4) / 5; + int maxScroll = Math.max(0, totalRows - 4); + scrollRows = Math.clamp(scrollRows, 0, maxScroll); + int thumbY = y + 24 + (maxScroll == 0 ? 0 : scrollRows * 143 / maxScroll); + graphics.blit(RenderPipelines.GUI_TEXTURED, SCROLLBAR, x + 217, thumbY, 20, draggingScrollbar ? 15 : 0, 12, 15, 32, 32); + graphics.fill(x + 365, y + 27, x + 381, y + 97, 0xFF303035); + graphics.fill(x + 366, y + 28, x + 380, y + 96, 0xFF6666AA); + int filled = 68 * menu.inkRemaining() / 16; + if (filled > 0) graphics.fill(x + 366, y + 96 - filled, x + 380, y + 96, 0xFF15151B); + } + + private void drawTabControl(GuiGraphicsExtractor graphics, int y, int textureY, boolean enabled) { + graphics.blit(RenderPipelines.GUI_TEXTURED, LEGACY_DESK, leftPos, topPos + y, 0, textureY, 58, 9, 256, 256, + enabled ? -1 : 0xFF666666); + } + + @Override protected void extractLabels(GuiGraphicsExtractor graphics, int mouseX, int mouseY) { + // Legacy leaves the inventory grid unlabeled. + } + + @Override public void extractContents(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float partialTick) { + super.extractContents(graphics, mouseX, mouseY, partialTick); + if (sortAlphabetically) graphics.outline(leftPos + 58, topPos, 20, 18, 0xFFFFD061); + if (allMode) graphics.outline(leftPos + 80, topPos, 28, 18, 0xFFFFD061); + renderCollection(graphics); + if (draggingPage && pageDragSource != null) { + DisplayPage target = pageAtMouse(mouseX, mouseY); + if (target != null && canMovePage(pageDragSource, target)) { + int x = leftPos + 58 + ((int) mouseX - leftPos - 58) / 31 * 31; + int y = topPos + 20 + ((int) mouseY - topPos - 20) / 41 * 41; + graphics.outline(x, y, 30, 40, 0xFF57D478); + } + graphics.fakeItem(pageDragSource.stack(), mouseX - 8, mouseY - 8); + } + + } + + @Override protected void extractTooltip(GuiGraphicsExtractor graphics, int mouseX, int mouseY) { + super.extractTooltip(graphics, mouseX, mouseY); + if (mouseX >= leftPos + 365 && mouseX < leftPos + 381 + && mouseY >= topPos + 27 && mouseY < topPos + 97) { + graphics.setTooltipForNextFrame(font, Component.translatable("screen.rivencraft.writing_desk.ink", + menu.inkRemaining(), 16), mouseX, mouseY); + } + if (mouseX >= leftPos + 213 && mouseX < leftPos + 231 + && mouseY >= topPos && mouseY < topPos + 18) { + graphics.setTooltipForNextFrame(font, Component.translatable("screen.rivencraft.writing_desk.category", + selectedCategory == null + ? Component.translatable("screen.rivencraft.writing_desk.all_categories") + : Component.literal(selectedCategory.toString())), mouseX, mouseY); + } + if (mouseX >= leftPos && mouseX < leftPos + 58) { + int localY = mouseY - topPos; + if (localY >= 20 && localY < 29) + graphics.setTooltipForNextFrame(font, Component.translatable("screen.rivencraft.writing_desk.previous_tabs"), mouseX, mouseY); + else if (localY >= 177 && localY < 186) + graphics.setTooltipForNextFrame(font, Component.translatable("screen.rivencraft.writing_desk.next_tabs"), mouseX, mouseY); + else if (mouseX < leftPos + 34 && localY >= 29 && localY < 177) { + int tab = menu.firstVisibleTab() + (localY - 29) / 37; + graphics.setTooltipForNextFrame(font, Component.translatable("screen.rivencraft.writing_desk.tab_symbol", + tab), mouseX, mouseY); + } + } + } + + private List pages(int collectionIndex) { + ItemStack collection = menu.getSlot(collectionIndex).getItem(); + if (collection.is(Rivencraft.PAGE.get())) return List.of(collection); + if (CollationFolderItem.isCollection(collection)) { + return collection.getOrDefault(Rivencraft.NOTEBOOK_CONTENTS.get(), NotebookContents.EMPTY).pages(); + } + return List.of(); + } + + private void renderCollection(GuiGraphicsExtractor graphics) { + if (lastCollection != menu.activeCollection()) { + lastCollection = menu.activeCollection(); + scrollRows = 0; + selectedPage = 0; + selectedCollection = -1; + } + List entries = filteredPages(); + for (int i = scrollRows * 5; i < entries.size() && i < scrollRows * 5 + 20; i++) { + int column = i % 5; + int row = i / 5 - scrollRows; + DisplayPage entry = entries.get(i); + ItemStack page = entry.stack(); + int x = leftPos + 58 + column * 31; + int y = topPos + 20 + row * 41; + drawPage(graphics, page, x, y); + if (page.isEmpty()) graphics.fill(x, y, x + 30, y + 40, 0xB8000000); + int active = selectedCollection >= 0 ? selectedCollection : menu.activeCollection(); + if (entry.collection() == active && entry.index() == selectedPage && !page.isEmpty()) + graphics.outline(x, y, 30, 40, 0xFFFFD061); + } + int active = selectedCollection >= 0 ? selectedCollection : menu.activeCollection(); + List source = pages(active); + if (selectedPage >= source.size() || source.get(selectedPage).isEmpty()) return; + ItemStack selected = source.get(selectedPage); + drawPage(graphics, selected, leftPos + 265, topPos + 26); + graphics.fill(leftPos + 261, topPos + 81, leftPos + 360, topPos + 95, 0xFF202020); + var symbol = PageItem.content(selected).symbol(); + if (symbol.isPresent() && PageItem.isCorrupted(symbol.orElseThrow())) { + graphics.enableScissor(leftPos + 263, topPos + 82, leftPos + 358, topPos + 94); + graphics.text(font, selected.getHoverName(), leftPos + 263, topPos + 84, + 0xFFE0E0E0, false); + graphics.disableScissor(); + } else { + String title = font.plainSubstrByWidth(selected.getHoverName().getString(), 95); + graphics.text(font, title, leftPos + 263, topPos + 84, 0xFFE0E0E0, false); + } + } + + private void drawPage(GuiGraphicsExtractor graphics, ItemStack page, int x, int y) { + graphics.nextStratum(); + graphics.blit(RenderPipelines.GUI_TEXTURED, PAGE_ATLAS, x, y, 156, 0, 30, 40, 256, 256); + if (page.isEmpty()) return; + if (page.has(Rivencraft.SYMBOL_PAGE_ID.get())) { + graphics.nextStratum(); + ItemStack glyph = page.copy(); + glyph.set(Rivencraft.PAGE_GLYPH_ONLY.get(), true); + graphics.pose().pushMatrix(); + graphics.pose().translate(x + 1, y + 6); + graphics.pose().scale(1.75F, 1.75F); + graphics.fakeItem(glyph, 0, 0); + graphics.pose().popMatrix(); + graphics.nextStratum(); + if (!net.dnorberg.rivencraft.writing.PortAuthoredWriting.isPageImplemented(page.get(Rivencraft.SYMBOL_PAGE_ID.get()))) + graphics.fill(x, y, x + 30, y + 40, 0x55FF3333); + } + } + + private static void drawNumeral(GuiGraphicsExtractor graphics, int number, int x, int y, int size) { + int[] digits = net.dnorberg.rivencraft.writing.PortSymbolNumerals.digitsFor(number); + int digitSize = digits.length == 1 ? size : Math.max(8, size * 3 / 4); + Identifier components = Identifier.fromNamespaceAndPath("rivencraft", "textures/gui/symbolcomponents.png"); + for (int digit : digits) { + for (int component : net.dnorberg.rivencraft.writing.PortSymbolNumerals.componentsForDigit(digit)) + graphics.blit(RenderPipelines.GUI_TEXTURED, components, x, y + (size - digitSize) / 2, + (component % 8) * 64, (component / 8) * 64, digitSize, digitSize, 64, 64, 512, 512, 0xFF211A14); + x += digitSize + 1; + } + } + + private DisplayPage pageAtMouse(double mouseX, double mouseY) { + int x = (int) Math.floor(mouseX - leftPos - 58); + int y = (int) Math.floor(mouseY - topPos - 20); + if (x < 0 || x >= 155 || y < 0 || y >= 164 || x % 31 >= 30 || y % 41 >= 40) return null; + int index = (y / 41 + scrollRows) * 5 + x / 31; + List entries = filteredPages(); + return index < entries.size() ? entries.get(index) : null; + } + + private boolean canMovePage(DisplayPage source, DisplayPage target) { + return !source.stack().isEmpty() + && (CollationFolderItem.isCollection(menu.getSlot(source.collection()).getItem()) + || menu.getSlot(source.collection()).getItem().is(Rivencraft.PAGE.get())) + && (CollationFolderItem.isCollection(menu.getSlot(target.collection()).getItem()) + || menu.getSlot(target.collection()).getItem().is(Rivencraft.PAGE.get())) + && (source.collection() != target.collection() || source.index() != target.index()); + } + + private int extractTargetSlot(double mouseX, double mouseY) { + for (Slot slot : menu.slots) { + int index = slot.index; + if (index >= WritingDeskBlockEntity.COLLECTIONS && index <= WritingDeskMenu.COPY_RESULT) continue; + if (!slot.isActive() || !isHovering(slot.x, slot.y, 16, 16, mouseX, mouseY)) continue; + ItemStack held = pageDragSource.stack(); + if (!slot.mayPlace(held)) return -1; + ItemStack current = slot.getItem(); + if (current.isEmpty()) return index; + if (index < WritingDeskBlockEntity.COLLECTIONS && CollationFolderItem.isCollection(current)) { + List pages = current.getOrDefault( + Rivencraft.NOTEBOOK_CONTENTS.get(), NotebookContents.EMPTY).pages(); + if (pages.size() < CollationFolderItem.capacity(current) || pages.stream().anyMatch(ItemStack::isEmpty)) + return index; + } else if (index > WritingDeskMenu.COPY_RESULT && ItemStack.isSameItemSameComponents(current, held) + && current.getCount() < slot.getMaxStackSize(held)) return index; + return -1; + } + return -1; + } + + @Override public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) { + double mouseX = event.x(), mouseY = event.y(); + int button = event.button(); + if (button == InputConstants.MOUSE_BUTTON_LEFT) { + pageDragSource = null; + draggingPage = false; + } + int surfaceX = (int) Math.floor(mouseX - leftPos - 58); + int surfaceY = (int) Math.floor(mouseY - topPos - 20); + if ((button == InputConstants.MOUSE_BUTTON_LEFT || button == InputConstants.MOUSE_BUTTON_RIGHT) && !menu.getCarried().isEmpty() + && surfaceX >= 0 && surfaceX < 155 && surfaceY >= 0 && surfaceY < 164) { + if (menu.getCarried().is(Rivencraft.PAGE.get()) + && PageItem.content(menu.getCarried()).kind() != PageContent.Kind.LINK_PANEL + && surfaceX % 31 < 30 && surfaceY % 41 < 40) { + int index = (surfaceY / 41 + scrollRows) * 5 + surfaceX / 31; + List entries = filteredPages(); + // Only displayed positions are addressable. Filtering never turns + // empty screen space into additional storage for a loose-page tab. + int collection = index < entries.size() ? entries.get(index).collection() : menu.activeCollection(); + int page = index < entries.size() ? entries.get(index).index() : index; + if (index < entries.size() && page < menu.pageCapacity(collection) + && minecraft != null && minecraft.gameMode != null) { + minecraft.gameMode.handleInventoryButtonClick(menu.containerId, + WritingDeskMenu.PLACE_PAGE_BUTTON_BASE + collection * NotebookContents.MAX_PAGES + page); + } + } + return true; + } + if (button == InputConstants.MOUSE_BUTTON_LEFT) { + if (mouseX >= leftPos && mouseX < leftPos + 58) { + int y = (int) mouseY - topPos; + if (y >= 20 && y < 29 || y >= 177 && y < 186) { + if (minecraft != null && minecraft.gameMode != null) + minecraft.gameMode.handleInventoryButtonClick(menu.containerId, + y < 29 ? WritingDeskMenu.PREVIOUS_TABS : WritingDeskMenu.NEXT_TABS); + return true; + } + } + if (mouseX >= leftPos + 213 && mouseX < leftPos + 233 + && mouseY >= topPos + 20 && mouseY < topPos + 186) { + draggingScrollbar = true; + setScrollFromMouse(mouseY); + return true; + } + int x = (int) Math.floor(mouseX - leftPos - 58); + int y = (int) Math.floor(mouseY - topPos - 20); + if (x >= 0 && x < 155 && y >= 0 && y < 164) { + DisplayPage entry = pageAtMouse(mouseX, mouseY); + if (entry != null && !entry.stack().isEmpty()) { + selectedCollection = entry.collection(); + selectedPage = entry.index(); + if (menu.getCarried().isEmpty()) { + pageDragSource = entry; + pageDragStartX = mouseX; + pageDragStartY = mouseY; + } + if (minecraft != null && minecraft.gameMode != null) { + int id = WritingDeskMenu.ALL_PAGE_BUTTON_BASE + + entry.collection() * NotebookContents.MAX_PAGES + entry.index(); + minecraft.gameMode.handleInventoryButtonClick(menu.containerId, id); + } + } + return true; + } + } + if (button == InputConstants.MOUSE_BUTTON_LEFT && mouseX >= leftPos && mouseX < leftPos + 58) { + for (int i = 0; i < 4; i++) { + int top = topPos + 29 + i * 37; + boolean overSlot = mouseX >= leftPos + 36 && mouseX < leftPos + 54 + && mouseY >= top + 4 && mouseY < top + 22; + if (mouseY >= top && mouseY < top + 37 && !overSlot) { + allMode = false; + selectedCollection = -1; + if (minecraft != null && minecraft.gameMode != null) { + minecraft.gameMode.handleInventoryButtonClick(menu.containerId, menu.firstVisibleTab() + i); + } + return true; + } + } + } + return super.mouseClicked(event, doubleClick); + } + + @Override public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) { + if (mouseX >= leftPos + 58 && mouseX < leftPos + 233 + && mouseY >= topPos + 20 && mouseY < topPos + 186) { + int rows = (filteredPages().size() + 4) / 5; + scrollRows = Math.clamp(scrollRows - (int) Math.signum(scrollY), 0, Math.max(0, rows - 4)); + return true; + } + return super.mouseScrolled(mouseX, mouseY, scrollX, scrollY); + } + + private void setScrollFromMouse(double mouseY) { + int rows = (filteredPages().size() + 4) / 5; + int max = Math.max(0, rows - 4); + scrollRows = Math.clamp((int) Math.round((mouseY - topPos - 31.5) * max / 143.0), 0, max); + } + + @Override public boolean mouseDragged(MouseButtonEvent event, double dragX, double dragY) { + double mouseX = event.x(), mouseY = event.y(); + int button = event.button(); + if (button == InputConstants.MOUSE_BUTTON_LEFT && pageDragSource != null) { + if (Math.abs(mouseX - pageDragStartX) >= 4 || Math.abs(mouseY - pageDragStartY) >= 4) + draggingPage = true; + return true; + } + if (button == InputConstants.MOUSE_BUTTON_LEFT && draggingScrollbar) { + setScrollFromMouse(mouseY); + return true; + } + return super.mouseDragged(event, dragX, dragY); + } + + @Override public boolean mouseReleased(MouseButtonEvent event) { + double mouseX = event.x(), mouseY = event.y(); + int button = event.button(); + if (button == InputConstants.MOUSE_BUTTON_LEFT && pageDragSource != null) { + if (draggingPage) { + DisplayPage target = pageAtMouse(mouseX, mouseY); + if (target != null && canMovePage(pageDragSource, target) + && minecraft != null && minecraft.gameMode != null) { + selectedCollection = target.collection(); + selectedPage = target.index(); + minecraft.gameMode.handleInventoryButtonClick(menu.containerId, + WritingDeskMenu.movePageButtonId(pageDragSource.collection(), pageDragSource.index(), + target.collection(), target.index())); + } else if (target == null && minecraft != null && minecraft.gameMode != null) { + int targetSlot = extractTargetSlot(mouseX, mouseY); + minecraft.gameMode.handleInventoryButtonClick(menu.containerId, + WritingDeskMenu.extractPageButtonId(pageDragSource.collection(), pageDragSource.index(), + targetSlot)); + } + } + pageDragSource = null; + draggingPage = false; + return true; + } + if (button == InputConstants.MOUSE_BUTTON_LEFT && draggingScrollbar) { + draggingScrollbar = false; + return true; + } + return super.mouseReleased(event); + } + + @Override public boolean keyPressed(KeyEvent event) { + if (event.isEscape()) return super.keyPressed(event); + if (search != null && search.isFocused()) { + search.keyPressed(event); + return true; + } + if (minecraft != null && minecraft.gameMode != null) { + if (event.key() == InputConstants.KEY_PAGEUP) { + minecraft.gameMode.handleInventoryButtonClick(menu.containerId, WritingDeskMenu.PREVIOUS_TABS); + return true; + } + if (event.key() == InputConstants.KEY_PAGEDOWN) { + minecraft.gameMode.handleInventoryButtonClick(menu.containerId, WritingDeskMenu.NEXT_TABS); + return true; + } + } + return super.keyPressed(event); + } +} diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/entity/DroppedBookEntity.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/entity/DroppedBookEntity.java new file mode 100644 index 0000000..84df26b --- /dev/null +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/entity/DroppedBookEntity.java @@ -0,0 +1,51 @@ +package net.dnorberg.rivencraft.entity; + +import net.dnorberg.rivencraft.Rivencraft; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.storage.ValueInput; +import net.minecraft.world.level.storage.ValueOutput; +import net.minecraft.world.phys.Vec3; + +/** Recoverable open-book entity, with the 1.21.1 decay and weather behavior. */ +public final class DroppedBookEntity extends ItemEntity { + private static final int SLOW_DECAY_INTERVAL = 10_000; + private int decayTicks; + + public DroppedBookEntity(EntityType type, Level level) { super(type, level); } + + public DroppedBookEntity(Level level, Vec3 position, ItemStack book) { + this(Rivencraft.DROPPED_BOOK.get(), level); + setPos(position.x, position.y + 0.25, position.z); + setDeltaMovement(Vec3.ZERO); + setItem(book); + setUnlimitedLifetime(); + setPickUpDelay(20); + } + + @Override + public void tick() { + super.tick(); + if (!(level() instanceof ServerLevel serverLevel) || isRemoved()) return; + if (++decayTicks >= SLOW_DECAY_INTERVAL) { + decayTicks = 0; + hurtServer(serverLevel, damageSources().starve(), 1.0F); + } + if (!isRemoved() && isInWaterOrRain()) hurtServer(serverLevel, damageSources().drown(), 1.0F); + } + + @Override + protected void addAdditionalSaveData(ValueOutput output) { + super.addAdditionalSaveData(output); + output.putInt("BookDecayTicks", decayTicks); + } + + @Override + protected void readAdditionalSaveData(ValueInput input) { + super.readAdditionalSaveData(input); + decayTicks = Math.clamp(input.getIntOr("BookDecayTicks", 0), 0, SLOW_DECAY_INTERVAL - 1); + } +} diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/item/CollationFolderItem.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/item/CollationFolderItem.java new file mode 100644 index 0000000..eafb0f4 --- /dev/null +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/item/CollationFolderItem.java @@ -0,0 +1,25 @@ +package net.dnorberg.rivencraft.item; + +import net.dnorberg.rivencraft.Rivencraft; +import net.dnorberg.rivencraft.writing.NotebookContents; +import net.minecraft.world.item.ItemStack; + +/** Legacy's compact page collection, using the existing Notebook page editor. */ +public final class CollationFolderItem extends NotebookItem { + public static final int MAX_PAGES = 27; + + public CollationFolderItem(Properties properties) { + super(properties); + } + + public static boolean isCollection(ItemStack stack) { + return stack.is(Rivencraft.NOTEBOOK.get()) || stack.is(Rivencraft.COLLATION_FOLDER.get()) + || stack.is(Rivencraft.SYMBOL_PORTFOLIO.get()); + } + + public static int capacity(ItemStack stack) { + return stack.is(Rivencraft.COLLATION_FOLDER.get()) ? MAX_PAGES + : stack.is(Rivencraft.NOTEBOOK.get()) || stack.is(Rivencraft.SYMBOL_PORTFOLIO.get()) + ? NotebookContents.MAX_PAGES : 0; + } +} diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/item/NotebookItem.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/item/NotebookItem.java new file mode 100644 index 0000000..542e25a --- /dev/null +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/item/NotebookItem.java @@ -0,0 +1,44 @@ +package net.dnorberg.rivencraft.item; + +import java.util.function.Consumer; +import net.dnorberg.rivencraft.Rivencraft; +import net.dnorberg.rivencraft.menu.NotebookMenu; +import net.dnorberg.rivencraft.writing.NotebookContents; +import net.minecraft.ChatFormatting; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.SimpleMenuProvider; +import net.minecraft.world.entity.player.Player; +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; +import net.minecraft.world.level.Level; + +/** The 1.21.1 collection editor opened through a server-owned menu. */ +public class NotebookItem extends Item { + public NotebookItem(Properties properties) { super(properties); } + + @Override + public InteractionResult use(Level level, Player player, InteractionHand hand) { + ItemStack notebook = player.getItemInHand(hand); + if (player instanceof ServerPlayer serverPlayer) { + serverPlayer.openMenu(new SimpleMenuProvider( + (id, inventory, ignored) -> new NotebookMenu(id, inventory, hand), notebook.getHoverName()), + buffer -> buffer.writeBoolean(hand == InteractionHand.OFF_HAND)); + } + return InteractionResult.SUCCESS; + } + + @Override + public void appendHoverText(ItemStack stack, TooltipContext context, TooltipDisplay display, + Consumer tooltip, TooltipFlag flag) { + var contents = stack.getOrDefault(Rivencraft.NOTEBOOK_CONTENTS.get(), NotebookContents.EMPTY); + tooltip.accept(Component.translatable("item.rivencraft.notebook.pages", contents.pageCount(), + CollationFolderItem.capacity(stack)).withStyle(ChatFormatting.GRAY)); + if (contents.unresolvedCount() > 0) tooltip.accept(Component.translatable( + "item.rivencraft.notebook.unresolved", contents.unresolvedCount()).withStyle(ChatFormatting.RED)); + } +} 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 index 23fb881..0daf46c 100644 --- a/ports/mc263/src/main/java/net/dnorberg/rivencraft/item/PageItem.java +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/item/PageItem.java @@ -42,6 +42,22 @@ public final class PageItem extends Item { return stack; } + public static ItemStack createSymbolPage(net.dnorberg.rivencraft.writing.SymbolReference symbol) { + return createSymbolPage(symbol.id()); + } + + /** Resolved view of the port's existing components; no second saved schema. */ + public static net.dnorberg.rivencraft.writing.PageContent content(ItemStack stack) { + var id = stack.get(Rivencraft.SYMBOL_PAGE_ID.get()); + if (isLinkPanel(stack)) return net.dnorberg.rivencraft.writing.PageContent.LINK_PANEL; + return id == null ? net.dnorberg.rivencraft.writing.PageContent.BLANK + : net.dnorberg.rivencraft.writing.PageContent.symbol( + new net.dnorberg.rivencraft.writing.SymbolReference(id)).resolve(); + } + + public static boolean isUsableWriting(ItemStack stack) { return content(stack).isResolvedSymbol(); } + public static boolean isCorrupted(net.dnorberg.rivencraft.writing.SymbolReference symbol) { return isCorrupted(symbol.id()); } + @Override public Component getName(ItemStack stack) { Component customName = stack.get(DataComponents.CUSTOM_NAME); diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/item/PortHandheldTravel.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/item/PortHandheldTravel.java new file mode 100644 index 0000000..6e9f2ad --- /dev/null +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/item/PortHandheldTravel.java @@ -0,0 +1,35 @@ +package net.dnorberg.rivencraft.item; + +import java.util.function.Supplier; +import net.dnorberg.rivencraft.entity.DroppedBookEntity; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.Prediction; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.item.ItemStack; + +/** Leaves only the active book behind after successful handheld travel. */ +public final class PortHandheldTravel { + private PortHandheldTravel() {} + + public static String travel(ServerPlayer player, InteractionHand hand, ItemStack book, + Supplier travel) { + if (book.isEmpty() || player.getItemInHand(hand) != book) return "Book is no longer in the active hand"; + var source = player.level(); + var departure = player.position(); + player.setItemInHand(hand, ItemStack.EMPTY); + boolean dropped = false; + try { + String failure = travel.get(); + if (failure != null) return failure; + dropped = source.addFreshEntity(new DroppedBookEntity(source, departure, book)); + return null; + } finally { + // Rejected travel or a cancelled entity spawn must not consume the book. + if (!dropped) { + if (player.getItemInHand(hand).isEmpty()) player.setItemInHand(hand, book); + else if (!player.getInventory().add(book)) player.drop(book, false, Prediction.SERVER_ONLY); + } + player.containerMenu.broadcastChanges(); + } + } +} diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/item/SealedNotebookItem.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/item/SealedNotebookItem.java new file mode 100644 index 0000000..91952a8 --- /dev/null +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/item/SealedNotebookItem.java @@ -0,0 +1,43 @@ +package net.dnorberg.rivencraft.item; + +import java.util.ArrayList; +import net.dnorberg.rivencraft.Rivencraft; +import net.dnorberg.rivencraft.writing.NotebookContents; +import net.dnorberg.rivencraft.writing.PortSymbolPages; +import net.minecraft.server.level.ServerPlayer; +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.level.Level; + +/** The 1.21.1 booster item, opened into a 16-page Collation Folder. */ +public final class SealedNotebookItem extends Item { + public SealedNotebookItem(Properties properties) { super(properties); } + + @Override + public InteractionResult use(Level level, Player player, InteractionHand hand) { + if (level.isClientSide()) return InteractionResult.SUCCESS; + if (!(player instanceof ServerPlayer serverPlayer)) return InteractionResult.PASS; + ItemStack sealed = player.getItemInHand(hand); + ItemStack folder = open(serverPlayer); + if (folder.isEmpty()) return InteractionResult.FAIL; + if (!player.hasInfiniteMaterials()) sealed.shrink(1); + if (sealed.isEmpty()) player.setItemInHand(hand, folder); + else if (!player.getInventory().add(folder)) player.drop(folder, false, Prediction.SERVER_ONLY); + return InteractionResult.SUCCESS; + } + + public static ItemStack open(ServerPlayer player) { + var candidates = PortSymbolPages.sealedCandidateIds(); + if (candidates.isEmpty()) return ItemStack.EMPTY; + var pages = new ArrayList(16); + for (int i = 0; i < 16; i++) pages.add(PageItem.createSymbolPage( + candidates.get(player.getRandom().nextInt(candidates.size())))); + ItemStack folder = new ItemStack(Rivencraft.COLLATION_FOLDER.get()); + folder.set(Rivencraft.NOTEBOOK_CONTENTS.get(), new NotebookContents(pages)); + return folder; + } +} diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/item/SymbolPortfolioItem.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/item/SymbolPortfolioItem.java new file mode 100644 index 0000000..fa030d0 --- /dev/null +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/item/SymbolPortfolioItem.java @@ -0,0 +1,39 @@ +package net.dnorberg.rivencraft.item; + +import java.util.ArrayList; +import java.util.List; +import net.dnorberg.rivencraft.Rivencraft; +import net.dnorberg.rivencraft.writing.NotebookContents; +import net.minecraft.core.component.DataComponents; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.Identifier; +import net.minecraft.world.item.ItemStack; + +/** Non-craftable, prefilled Legacy-style collections of one symbol-page category. */ +public final class SymbolPortfolioItem extends NotebookItem { + public SymbolPortfolioItem(Properties properties) { + super(properties); + } + + public static List createVolumes(Identifier category, List pages) { + if (pages.isEmpty()) return List.of(); + int capacity = NotebookContents.MAX_PAGES; + int volumeCount = (pages.size() + capacity - 1) / capacity; + List volumes = new ArrayList<>(volumeCount); + for (int volume = 0; volume < volumeCount; volume++) { + ItemStack portfolio = new ItemStack(Rivencraft.SYMBOL_PORTFOLIO.get()); + int first = volume * capacity; + int last = Math.min(first + capacity, pages.size()); + portfolio.set(Rivencraft.NOTEBOOK_CONTENTS.get(), + new NotebookContents(pages.subList(first, last))); + Component categoryName = Component.translatableWithFallback( + "itemGroup.rivencraft.pages." + category.getPath(), category.getPath()); + portfolio.set(DataComponents.CUSTOM_NAME, volumeCount == 1 + ? Component.translatable("item.rivencraft.symbol_portfolio.named", categoryName) + : Component.translatable("item.rivencraft.symbol_portfolio.volume", + categoryName, volume + 1, volumeCount)); + volumes.add(portfolio); + } + return List.copyOf(volumes); + } +} diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/menu/NotebookMenu.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/menu/NotebookMenu.java new file mode 100644 index 0000000..de190e3 --- /dev/null +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/menu/NotebookMenu.java @@ -0,0 +1,205 @@ +package net.dnorberg.rivencraft.menu; + +import java.util.List; +import net.dnorberg.rivencraft.Rivencraft; +import net.dnorberg.rivencraft.writing.NotebookContents; +import net.dnorberg.rivencraft.item.PageItem; +import net.dnorberg.rivencraft.item.CollationFolderItem; +import net.minecraft.core.NonNullList; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.world.Container; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.ContainerInput; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; + +public final class NotebookMenu extends AbstractContainerMenu { + public static final int NOTEBOOK_SLOTS = NotebookContents.MAX_PAGES; + public static final int MOVE_PREVIOUS_BASE = 0; + public static final int MOVE_NEXT_BASE = NOTEBOOK_SLOTS; + private static final int PLAYER_INVENTORY_START = NOTEBOOK_SLOTS; + private final Inventory playerInventory; + private final InteractionHand hand; + private final int openedHotbarSlot; + private final NotebookContainer contents; + private final int pageCapacity; + + public NotebookMenu(int containerId, Inventory inventory, RegistryFriendlyByteBuf data) { + this(containerId, inventory, data.readBoolean() ? InteractionHand.OFF_HAND : InteractionHand.MAIN_HAND); + } + + public NotebookMenu(int containerId, Inventory inventory, InteractionHand hand) { + super(Rivencraft.NOTEBOOK_MENU.get(), containerId); + this.playerInventory = inventory; + this.hand = hand; + this.openedHotbarSlot = inventory.getSelectedSlot(); + this.pageCapacity = CollationFolderItem.capacity(inventory.player.getItemInHand(hand)); + this.contents = new NotebookContainer(inventory, hand); + + for (int page = 0; page < NOTEBOOK_SLOTS; page++) { + // The client draws these pages on a filtered, scrollable surface instead. + addSlot(new PageSlot(contents, page, -1000, -1000, page < pageCapacity)); + } + for (int row = 0; row < 3; row++) { + for (int column = 0; column < 9; column++) { + int inventoryIndex = column + row * 9 + 9; + addSlot(playerSlot(inventoryIndex, 8 + column * 18, 135 + row * 18)); + } + } + for (int column = 0; column < 9; column++) { + addSlot(playerSlot(column, 8 + column * 18, 193)); + } + } + + private Slot playerSlot(int inventoryIndex, int x, int y) { + boolean locked = hand == InteractionHand.MAIN_HAND && inventoryIndex == openedHotbarSlot; + return locked ? new LockedSlot(playerInventory, inventoryIndex, x, y) + : new Slot(playerInventory, inventoryIndex, x, y); + } + + public int pageCapacity() { return pageCapacity; } + + @Override + public boolean stillValid(Player player) { + return player.isAlive() + && (hand == InteractionHand.OFF_HAND || player.getInventory().getSelectedSlot() == openedHotbarSlot) + && CollationFolderItem.isCollection(player.getItemInHand(hand)); + } + + @Override + public ItemStack quickMoveStack(Player player, int index) { + Slot slot = slots.get(index); + if (!slot.hasItem()) { + return ItemStack.EMPTY; + } + ItemStack source = slot.getItem(); + ItemStack original = source.copy(); + if (index < PLAYER_INVENTORY_START) { + if (!moveItemStackTo(source, PLAYER_INVENTORY_START, slots.size(), true)) { + return ItemStack.EMPTY; + } + } else { + if (!source.is(Rivencraft.PAGE.get()) + || PageItem.isLinkPanel(source) + || !moveItemStackTo(source, 0, PLAYER_INVENTORY_START, false)) { + return ItemStack.EMPTY; + } + } + if (source.isEmpty()) { + slot.setByPlayer(ItemStack.EMPTY); + } else { + slot.setChanged(); + } + return original; + } + + @Override + public boolean clickMenuButton(Player player, int id) { + int source; + int target; + if (id >= MOVE_PREVIOUS_BASE && id < MOVE_NEXT_BASE) { + source = id; + target = source - 1; + } else if (id >= MOVE_NEXT_BASE && id < MOVE_NEXT_BASE + NOTEBOOK_SLOTS) { + source = id - MOVE_NEXT_BASE; + target = source + 1; + } else { + return false; + } + if (source < 0 || source >= pageCapacity || target < 0 || target >= pageCapacity + || contents.getItem(source).isEmpty()) { + return false; + } + contents.swap(source, target); + broadcastChanges(); + return true; + } + + @Override + public void clicked(int slotId, int button, ContainerInput clickType, Player player) { + if (!stillValid(player)) { + player.closeContainer(); + return; + } + if (clickType == ContainerInput.SWAP + && (button == openedHotbarSlot && hand == InteractionHand.MAIN_HAND + || button == 40 && hand == InteractionHand.OFF_HAND)) { + return; + } + super.clicked(slotId, button, clickType, player); + } + + private static final class PageSlot extends Slot { + private final boolean enabled; + + private PageSlot(Container container, int slot, int x, int y, boolean enabled) { + super(container, slot, x, y); + this.enabled = enabled; + } + + @Override public boolean mayPlace(ItemStack stack) { + return enabled && stack.is(Rivencraft.PAGE.get()) + && !PageItem.isLinkPanel(stack); + } + @Override public boolean mayPickup(Player player) { return enabled; } + @Override public int getMaxStackSize() { return 1; } + } + + private static final class LockedSlot extends Slot { + private LockedSlot(Container container, int slot, int x, int y) { + super(container, slot, x, y); + } + + @Override public boolean mayPickup(Player player) { return false; } + @Override public boolean mayPlace(ItemStack stack) { return false; } + } + + private static final class NotebookContainer extends SimpleContainer { + private final Inventory inventory; + private final InteractionHand hand; + private boolean loading; + + private NotebookContainer(Inventory inventory, InteractionHand hand) { + super(NOTEBOOK_SLOTS); + this.inventory = inventory; + this.hand = hand; + ItemStack notebook = inventory.player.getItemInHand(hand); + loading = true; + List saved = notebook.getOrDefault( + Rivencraft.NOTEBOOK_CONTENTS.get(), NotebookContents.EMPTY).pages(); + for (int index = 0; index < saved.size(); index++) { + super.setItem(index, saved.get(index).copy()); + } + loading = false; + } + + private void swap(int first, int second) { + loading = true; + ItemStack held = getItem(first); + setItem(first, getItem(second)); + setItem(second, held); + loading = false; + setChanged(); + } + + @Override + public void setChanged() { + super.setChanged(); + ItemStack notebook = inventory.player.getItemInHand(hand); + if (loading || !CollationFolderItem.isCollection(notebook)) { + return; + } + int capacity = CollationFolderItem.capacity(notebook); + NonNullList saved = NonNullList.withSize(capacity, ItemStack.EMPTY); + for (int index = 0; index < capacity; index++) { + ItemStack page = getItem(index); + saved.set(index, page.isEmpty() ? ItemStack.EMPTY : page.copyWithCount(1)); + } + notebook.set(Rivencraft.NOTEBOOK_CONTENTS.get(), new NotebookContents(saved)); + } + } +} diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/menu/PortBookBinderMenu.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/menu/PortBookBinderMenu.java index a9200f0..05751f1 100644 --- a/ports/mc263/src/main/java/net/dnorberg/rivencraft/menu/PortBookBinderMenu.java +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/menu/PortBookBinderMenu.java @@ -72,6 +72,14 @@ public final class PortBookBinderMenu extends AbstractContainerMenu { if (player.level().isClientSide()) return true; int index = id - STRIP_CLICK_BASE; ItemStack carried = getCarried(); + if (net.dnorberg.rivencraft.item.CollationFolderItem.isCollection(carried)) { + ItemStack emptied = blockEntity.importCollection(carried, + Math.min(index == 0 ? 0 : index - 1, blockEntity.nextPageIndex())); + if (emptied.isEmpty()) return false; + setCarried(emptied); + broadcastChanges(); + return true; + } if (!carried.isEmpty()) { if (index == 0) { if (!PageItem.isLinkPanel(carried) @@ -145,6 +153,12 @@ public final class PortBookBinderMenu extends AbstractContainerMenu { } else if (stack.is(Items.LEATHER)) { moved = moveItemStackTo(stack, PortBookBinderBlockEntity.COVER, PortBookBinderBlockEntity.COVER + 1, false); + } else if (net.dnorberg.rivencraft.item.CollationFolderItem.isCollection(stack)) { + ItemStack emptied = blockEntity.importCollection(stack, blockEntity.nextPageIndex()); + if (emptied.isEmpty()) return ItemStack.EMPTY; + slot.setByPlayer(emptied); + broadcastChanges(); + return original; } else if (PageItem.isLinkPanel(stack)) { moved = moveItemStackTo(stack, PortBookBinderBlockEntity.PANEL, PortBookBinderBlockEntity.PANEL + 1, false); diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/menu/WritingDeskMenu.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/menu/WritingDeskMenu.java new file mode 100644 index 0000000..8744631 --- /dev/null +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/menu/WritingDeskMenu.java @@ -0,0 +1,463 @@ +package net.dnorberg.rivencraft.menu; + +import net.dnorberg.rivencraft.Rivencraft; +import net.dnorberg.rivencraft.block.entity.WritingDeskBlockEntity; +import net.dnorberg.rivencraft.item.PageItem; +import net.dnorberg.rivencraft.writing.NotebookContents; +import net.dnorberg.rivencraft.item.CollationFolderItem; +import net.dnorberg.rivencraft.writing.PageContent; +import net.dnorberg.rivencraft.writing.PortSymbolPages; +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.Identifier; +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; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.DataSlot; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; + +public final class WritingDeskMenu extends AbstractContainerMenu { + public static final int COPY_RESULT = WritingDeskBlockEntity.SIZE; + public static final int VISIBLE_TABS = 4; + public static final int PREVIOUS_TABS = 90; + public static final int NEXT_TABS = 91; + public static final int PAGE_BUTTON_BASE = 128; + public static final int CATALOG_BUTTON_BASE = 512; + public static final int ALL_PAGE_BUTTON_BASE = 32768; + public static final int PLACE_PAGE_BUTTON_BASE = 65536; + private static final int PAGE_ADDRESS_COUNT = WritingDeskBlockEntity.COLLECTIONS * NotebookContents.MAX_PAGES; + public static final int MOVE_PAGE_BUTTON_BASE = PLACE_PAGE_BUTTON_BASE + PAGE_ADDRESS_COUNT; + private static final int PLAYER_START = COPY_RESULT + 1; + private static final int EXTRACT_TARGET_COUNT = PLAYER_START + 36 + 1; + public static final int EXTRACT_PAGE_BUTTON_BASE = MOVE_PAGE_BUTTON_BASE + PAGE_ADDRESS_COUNT * PAGE_ADDRESS_COUNT; + private final Container desk; + private final WritingDeskBlockEntity blockEntity; + private final SimpleContainer result = new SimpleContainer(1); + private final boolean clientSide; + private int syncedActiveCollection; + private int syncedInkRemaining; + private int firstVisibleTab; + private int selectedPage; + private int selectedCollection = -1; + private Identifier 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) { + super(Rivencraft.WRITING_DESK_MENU.get(), id); + 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); + firstVisibleTab = blockEntity == null ? 0 : blockEntity.activeCollection() / VISIBLE_TABS * VISIBLE_TABS; + addDataSlot(new DataSlot() { + @Override public int get() { return firstVisibleTab; } + @Override public void set(int value) { firstVisibleTab = value; } + }); + addDataSlot(new DataSlot() { + @Override public int get() { return WritingDeskMenu.this.blockEntity == null + ? syncedActiveCollection : WritingDeskMenu.this.blockEntity.activeCollection(); } + @Override public void set(int value) { syncedActiveCollection = value; } + }); + addDataSlot(new DataSlot() { + @Override public int get() { return WritingDeskMenu.this.blockEntity == null + ? syncedInkRemaining : WritingDeskMenu.this.blockEntity.inkRemaining(); } + @Override public void set(int value) { syncedInkRemaining = value; } + }); + for (int i = 0; i < WritingDeskBlockEntity.COLLECTIONS; i++) { + addSlot(new DeskSlot(desk, i, 37, 34 + (i % VISIBLE_TABS) * 37, 1)); + } + addSlot(new DeskSlot(desk, WritingDeskBlockEntity.INK, 385, 28, 16)); + addSlot(new DeskSlot(desk, WritingDeskBlockEntity.RETURN, 385, 80, 64)); + addSlot(new DeskSlot(desk, WritingDeskBlockEntity.PAPER, 241, 28, 64)); + addSlot(new DeskSlot(desk, WritingDeskBlockEntity.LEGACY_OUTPUT, -1000, -1000, 64)); + addSlot(new CopySlot(result, 0, 241, 80)); + for (int row = 0; row < 3; row++) { + for (int column = 0; column < 9; column++) { + addSlot(new Slot(inventory, column + row * 9 + 9, 241 + column * 18, 104 + row * 18)); + } + } + for (int column = 0; column < 9; column++) { + addSlot(new Slot(inventory, column, 241 + column * 18, 162)); + } + } + + public int activeCollection() { return syncedActiveCollection; } + public int firstVisibleTab() { return firstVisibleTab; } + public int inkRemaining() { return syncedInkRemaining; } + public int pageCapacity(int collectionIndex) { + if (collectionIndex < 0 || collectionIndex >= WritingDeskBlockEntity.COLLECTIONS) return 0; + ItemStack collection = desk.getItem(collectionIndex); + return CollationFolderItem.isCollection(collection) ? CollationFolderItem.capacity(collection) + : collection.is(Rivencraft.PAGE.get()) ? 1 : 0; + } + public List catalog() { return catalog; } + public void setCatalog(List symbols) { catalog = List.copyOf(symbols); } + + public static int movePageButtonId(int sourceCollection, int sourcePage, int targetCollection, int targetPage) { + int source = sourceCollection * NotebookContents.MAX_PAGES + sourcePage; + int target = targetCollection * NotebookContents.MAX_PAGES + targetPage; + return MOVE_PAGE_BUTTON_BASE + source * PAGE_ADDRESS_COUNT + target; + } + + /** targetSlot is -1 for the cursor, otherwise a visible player or desk-tab slot. */ + public static int extractPageButtonId(int sourceCollection, int sourcePage, int targetSlot) { + int source = sourceCollection * NotebookContents.MAX_PAGES + sourcePage; + return EXTRACT_PAGE_BUTTON_BASE + source * EXTRACT_TARGET_COUNT + targetSlot + 1; + } + + @Override public boolean clickMenuButton(Player player, int id) { + if (blockEntity == null || !stillValid(player)) return false; + if (id == PREVIOUS_TABS || id == NEXT_TABS) { + firstVisibleTab = Math.clamp(firstVisibleTab + (id == NEXT_TABS ? VISIBLE_TABS : -VISIBLE_TABS), + 0, WritingDeskBlockEntity.COLLECTIONS - VISIBLE_TABS); + return true; + } else if (id >= 0 && id < WritingDeskBlockEntity.COLLECTIONS) { + if (id < firstVisibleTab || id >= firstVisibleTab + VISIBLE_TABS) return false; + blockEntity.setActiveCollection(id); + selectedPage = 0; + selectedCollection = -1; + selectedSymbol = null; + } else if (id >= EXTRACT_PAGE_BUTTON_BASE + && id < EXTRACT_PAGE_BUTTON_BASE + PAGE_ADDRESS_COUNT * EXTRACT_TARGET_COUNT) { + int offset = id - EXTRACT_PAGE_BUTTON_BASE; + int source = offset / EXTRACT_TARGET_COUNT; + int targetSlot = offset % EXTRACT_TARGET_COUNT - 1; + if (!extractPage(source / NotebookContents.MAX_PAGES, source % NotebookContents.MAX_PAGES, + targetSlot)) return false; + } else if (id >= MOVE_PAGE_BUTTON_BASE && id < MOVE_PAGE_BUTTON_BASE + PAGE_ADDRESS_COUNT * PAGE_ADDRESS_COUNT) { + int offset = id - MOVE_PAGE_BUTTON_BASE; + int source = offset / PAGE_ADDRESS_COUNT; + int target = offset % PAGE_ADDRESS_COUNT; + if (!movePage(source / NotebookContents.MAX_PAGES, source % NotebookContents.MAX_PAGES, + target / NotebookContents.MAX_PAGES, target % NotebookContents.MAX_PAGES)) return false; + } else if (id >= PLACE_PAGE_BUTTON_BASE + && id < PLACE_PAGE_BUTTON_BASE + WritingDeskBlockEntity.COLLECTIONS * NotebookContents.MAX_PAGES) { + int offset = id - PLACE_PAGE_BUTTON_BASE; + if (!placeCarriedPage(offset / NotebookContents.MAX_PAGES, offset % NotebookContents.MAX_PAGES)) return false; + } else if (id >= PAGE_BUTTON_BASE && id < PAGE_BUTTON_BASE + NotebookContents.MAX_PAGES) { + selectedPage = id - PAGE_BUTTON_BASE; + selectedCollection = -1; + selectedSymbol = null; + } else if (id >= ALL_PAGE_BUTTON_BASE && id < ALL_PAGE_BUTTON_BASE + WritingDeskBlockEntity.COLLECTIONS * NotebookContents.MAX_PAGES) { + int offset = id - ALL_PAGE_BUTTON_BASE; + int collection = offset / NotebookContents.MAX_PAGES; + int page = offset % NotebookContents.MAX_PAGES; + if (pageAt(collection, page).isEmpty()) return false; + selectedCollection = collection; + selectedPage = page; + selectedSymbol = null; + } else if (id >= CATALOG_BUTTON_BASE && id < CATALOG_BUTTON_BASE + catalog.size()) { + List available = WritingDeskCatalog.available(blockEntity); + if (!catalog.equals(available)) { + refreshCatalog(available); + return false; + } + selectedSymbol = available.get(id - CATALOG_BUTTON_BASE).id(); + } else return false; + refreshOutput(); + return true; + } + + private boolean placeCarriedPage(int collectionIndex, int pageIndex) { + // Resolve the actual storage position on the server, never a filtered list index. + ItemStack carried = getCarried(); + if (clientSide || !storablePage(carried)) return false; + ItemStack collection = desk.getItem(collectionIndex); + if (CollationFolderItem.isCollection(collection)) { + if (pageIndex < 0 || pageIndex >= CollationFolderItem.capacity(collection)) return false; + var pages = new java.util.ArrayList<>(collection.getOrDefault( + Rivencraft.NOTEBOOK_CONTENTS.get(), NotebookContents.EMPTY).pages()); + while (pages.size() <= pageIndex) pages.add(ItemStack.EMPTY); + if (!pages.get(pageIndex).isEmpty()) return false; + pages.set(pageIndex, carried.copyWithCount(1)); + ItemStack updated = collection.copy(); + updated.set(Rivencraft.NOTEBOOK_CONTENTS.get(), new NotebookContents(pages)); + desk.setItem(collectionIndex, updated); + } else return false; + carried.shrink(1); + setCarried(carried.isEmpty() ? ItemStack.EMPTY : carried); + selectedCollection = collectionIndex; + selectedPage = pageIndex; + selectedSymbol = null; + refreshCatalog(); + broadcastChanges(); + return true; + } + + private boolean movePage(int sourceCollection, int sourcePage, int targetCollection, int targetPage) { + if (!getCarried().isEmpty() || sourceCollection == targetCollection && sourcePage == targetPage) return false; + ItemStack sourceBook = desk.getItem(sourceCollection); + ItemStack targetBook = desk.getItem(targetCollection); + boolean sourceNotebook = CollationFolderItem.isCollection(sourceBook); + boolean targetNotebook = CollationFolderItem.isCollection(targetBook); + if (sourceNotebook && sourcePage >= CollationFolderItem.capacity(sourceBook) + || targetNotebook && targetPage >= CollationFolderItem.capacity(targetBook)) return false; + if ((!sourceNotebook && (!sourceBook.is(Rivencraft.PAGE.get()) || sourcePage != 0)) + || (!targetNotebook && (!targetBook.is(Rivencraft.PAGE.get()) || targetPage != 0))) return false; + var sourcePages = sourceNotebook ? new java.util.ArrayList<>(sourceBook.getOrDefault( + Rivencraft.NOTEBOOK_CONTENTS.get(), NotebookContents.EMPTY).pages()) : null; + if (sourceNotebook && (sourcePage >= sourcePages.size() || sourcePages.get(sourcePage).isEmpty())) return false; + ItemStack moving = sourceNotebook ? sourcePages.get(sourcePage) : sourceBook; + if (moving.getCount() != 1) return false; + if (!storablePage(moving)) return false; + if (!targetNotebook && targetBook.getCount() != 1) return false; + var targetPages = targetNotebook ? sourceCollection == targetCollection ? sourcePages + : new java.util.ArrayList<>(targetBook.getOrDefault( + Rivencraft.NOTEBOOK_CONTENTS.get(), NotebookContents.EMPTY).pages()) : null; + if (targetNotebook) while (targetPages.size() <= targetPage) targetPages.add(ItemStack.EMPTY); + ItemStack displaced = targetNotebook ? targetPages.get(targetPage) : targetBook; + if (!displaced.isEmpty() && !storablePage(displaced)) return false; + if (targetNotebook) targetPages.set(targetPage, moving); + if (sourceNotebook) { + sourcePages.set(sourcePage, displaced); + ItemStack updatedSource = sourceBook.copy(); + updatedSource.set(Rivencraft.NOTEBOOK_CONTENTS.get(), new NotebookContents(sourcePages)); + desk.setItem(sourceCollection, updatedSource); + } else desk.setItem(sourceCollection, displaced.isEmpty() ? ItemStack.EMPTY : displaced.copyWithCount(1)); + if (targetNotebook && sourceCollection != targetCollection) { + ItemStack updatedTarget = targetBook.copy(); + updatedTarget.set(Rivencraft.NOTEBOOK_CONTENTS.get(), new NotebookContents(targetPages)); + desk.setItem(targetCollection, updatedTarget); + } else if (!targetNotebook) desk.setItem(targetCollection, moving.copyWithCount(1)); + selectedCollection = targetCollection; + selectedPage = targetPage; + selectedSymbol = null; + refreshCatalog(); + broadcastChanges(); + return true; + } + + private boolean extractPage(int sourceCollection, int sourcePage, int targetSlot) { + if (!getCarried().isEmpty() || targetSlot < -1 || targetSlot >= slots.size()) return false; + ItemStack sourceBook = desk.getItem(sourceCollection); + boolean sourceNotebook = CollationFolderItem.isCollection(sourceBook); + if (sourceNotebook && sourcePage >= CollationFolderItem.capacity(sourceBook)) return false; + if (!sourceNotebook && (!sourceBook.is(Rivencraft.PAGE.get()) || sourcePage != 0)) return false; + var sourcePages = sourceNotebook ? new java.util.ArrayList<>(sourceBook.getOrDefault( + Rivencraft.NOTEBOOK_CONTENTS.get(), NotebookContents.EMPTY).pages()) : null; + if (sourceNotebook && (sourcePage >= sourcePages.size() || sourcePages.get(sourcePage).isEmpty())) return false; + ItemStack moving = sourceNotebook ? sourcePages.get(sourcePage).copyWithCount(1) : sourceBook.copyWithCount(1); + if (!sourceNotebook && sourceBook.getCount() != 1) return false; + + if (targetSlot == -1) { + setCarried(moving); + } else { + if (targetSlot >= WritingDeskBlockEntity.COLLECTIONS && targetSlot < PLAYER_START) return false; + if (targetSlot < WritingDeskBlockEntity.COLLECTIONS && !storablePage(moving)) return false; + Slot target = slots.get(targetSlot); + if (!target.isActive() || !target.mayPlace(moving)) return false; + ItemStack current = target.getItem(); + if (targetSlot < WritingDeskBlockEntity.COLLECTIONS && CollationFolderItem.isCollection(current)) { + var targetPages = targetSlot == sourceCollection ? sourcePages + : new java.util.ArrayList<>(current.getOrDefault( + Rivencraft.NOTEBOOK_CONTENTS.get(), NotebookContents.EMPTY).pages()); + int position = -1; + for (int i = 0; i < CollationFolderItem.capacity(current); i++) { + if (i >= targetPages.size() || targetPages.get(i).isEmpty()) { + position = i; + break; + } + } + if (position < 0) return false; + while (targetPages.size() <= position) targetPages.add(ItemStack.EMPTY); + targetPages.set(position, moving); + if (targetSlot != sourceCollection) { + ItemStack updatedTarget = current.copy(); + updatedTarget.set(Rivencraft.NOTEBOOK_CONTENTS.get(), new NotebookContents(targetPages)); + target.setByPlayer(updatedTarget); + } + } else if (current.isEmpty()) { + target.setByPlayer(moving); + } else if (targetSlot >= PLAYER_START && ItemStack.isSameItemSameComponents(current, moving) + && current.getCount() < target.getMaxStackSize(moving)) { + target.setByPlayer(current.copyWithCount(current.getCount() + 1)); + } else return false; + } + + if (sourceNotebook) { + sourcePages.set(sourcePage, ItemStack.EMPTY); + ItemStack updatedSource = sourceBook.copy(); + updatedSource.set(Rivencraft.NOTEBOOK_CONTENTS.get(), new NotebookContents(sourcePages)); + desk.setItem(sourceCollection, updatedSource); + } else desk.setItem(sourceCollection, ItemStack.EMPTY); + selectedCollection = sourceCollection; + selectedPage = sourcePage; + selectedSymbol = null; + refreshCatalog(); + broadcastChanges(); + return true; + } + + @Override public boolean stillValid(Player player) { + return blockEntity != null && blockEntity.stillValid(player); + } + + @Override public ItemStack quickMoveStack(Player player, int index) { + if (!stillValid(player) || index < 0 || index >= slots.size()) return ItemStack.EMPTY; + if (index == COPY_RESULT) { + refreshOutput(); + if (!canCopy()) return ItemStack.EMPTY; + ItemStack copy = selectedSource().copyWithCount(1); + ItemStack movingCopy = copy.copy(); + if (!moveItemStackTo(movingCopy, PLAYER_START, slots.size(), true) || !movingCopy.isEmpty()) { + return ItemStack.EMPTY; + } + if (!blockEntity.consumeCopyMaterials()) return ItemStack.EMPTY; + refreshOutput(); + return copy; + } + Slot slot = slots.get(index); + if (!slot.isActive()) return ItemStack.EMPTY; + if (!slot.hasItem()) return ItemStack.EMPTY; + ItemStack moving = slot.getItem(); + ItemStack original = moving.copy(); + if (index < COPY_RESULT) { + if (!moveItemStackTo(moving, PLAYER_START, slots.size(), true)) return ItemStack.EMPTY; + } else if (!moveIntoDesk(moving)) { + return ItemStack.EMPTY; + } + if (moving.isEmpty()) slot.setByPlayer(ItemStack.EMPTY); + else slot.setChanged(); + return original; + } + + private boolean moveIntoDesk(ItemStack moving) { + if (moving.is(Rivencraft.INK_VIAL.get())) return moveItemStackTo(moving, + WritingDeskBlockEntity.INK, WritingDeskBlockEntity.INK + 1, false); + if (CollationFolderItem.isCollection(moving)) return moveItemStackTo(moving, firstVisibleTab, firstVisibleTab + VISIBLE_TABS, false); + if (moving.is(Items.PAPER)) + return moveItemStackTo(moving, WritingDeskBlockEntity.PAPER, + WritingDeskBlockEntity.PAPER + 1, false); + if (storablePage(moving)) return moveItemStackTo(moving, firstVisibleTab, firstVisibleTab + VISIBLE_TABS, false); + return false; + } + + private static boolean storablePage(ItemStack stack) { + return stack.is(Rivencraft.PAGE.get()) + && PageItem.content(stack).kind() != PageContent.Kind.LINK_PANEL; + } + + private ItemStack selectedSource() { + if (selectedSymbol != null) { + SymbolReference reference = new SymbolReference(selectedSymbol); + return PortSymbolPages.find(reference.id()) != null + && blockEntity != null + && WritingDeskCatalog.available(blockEntity).stream() + .anyMatch(entry -> entry.id().equals(selectedSymbol)) + ? PageItem.createSymbolPage(reference) : ItemStack.EMPTY; + } + int collectionIndex = selectedCollection >= 0 ? selectedCollection + : blockEntity == null ? syncedActiveCollection : blockEntity.activeCollection(); + return pageAt(collectionIndex, selectedPage); + } + + private ItemStack pageAt(int collectionIndex, int pageIndex) { + if (collectionIndex < 0 || collectionIndex >= WritingDeskBlockEntity.COLLECTIONS + || pageIndex < 0 || pageIndex >= NotebookContents.MAX_PAGES) return ItemStack.EMPTY; + ItemStack collection = desk.getItem(collectionIndex); + if (collection.is(Rivencraft.PAGE.get())) return pageIndex == 0 ? collection : ItemStack.EMPTY; + if (CollationFolderItem.isCollection(collection)) { + if (pageIndex >= CollationFolderItem.capacity(collection)) return ItemStack.EMPTY; + List pages = collection.getOrDefault( + Rivencraft.NOTEBOOK_CONTENTS.get(), NotebookContents.EMPTY).pages(); + return pageIndex < pages.size() ? pages.get(pageIndex) : ItemStack.EMPTY; + } + return ItemStack.EMPTY; + } + + private boolean canCopy() { + ItemStack source = selectedSource(); + return blockEntity != null && source.is(Rivencraft.PAGE.get()) + && PageItem.content(source).kind() != PageContent.Kind.BLANK + && PageItem.content(source).kind() != PageContent.Kind.LINK_PANEL + && PageItem.isUsableWriting(source) && blockEntity.canConsumeCopyMaterials(); + } + + private void refreshOutput() { + if (clientSide) return; + if (blockEntity != null) blockEntity.setDisplayPage(selectedSource()); + ItemStack preview = canCopy() ? selectedSource().copyWithCount(1) : ItemStack.EMPTY; + if (!ItemStack.matches(result.getItem(0), preview)) result.setItem(0, preview); + } + + private void refreshCatalog() { + if (clientSide || blockEntity == null) return; + refreshCatalog(WritingDeskCatalog.available(blockEntity)); + } + + private void refreshCatalog(List current) { + if (clientSide || blockEntity == null) return; + 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(); + } + + @Override public void clicked(int slotId, int button, net.minecraft.world.inventory.ContainerInput clickType, + Player player) { + if (!stillValid(player)) { + player.closeContainer(); + return; + } + refreshCatalog(); + refreshOutput(); + if (slotId >= 0 && slotId < slots.size() && !slots.get(slotId).isActive()) return; + super.clicked(slotId, button, clickType, player); + } + + private final class CopySlot extends Slot { + private CopySlot(Container container, int index, int x, int y) { super(container, index, x, y); } + @Override public boolean mayPlace(ItemStack stack) { return false; } + @Override public boolean mayPickup(Player player) { + return clientSide ? hasItem() : WritingDeskMenu.this.stillValid(player) && canCopy(); + } + @Override public void onTake(Player player, ItemStack stack) { + if (!clientSide && blockEntity.consumeCopyMaterials()) { + refreshOutput(); + } + super.onTake(player, stack); + } + } + + private final class DeskSlot extends Slot { + private final int limit; + private DeskSlot(Container container, int index, int x, int y, int limit) { + super(container, index, x, y); + this.limit = limit; + } + @Override public boolean isActive() { + int slot = getContainerSlot(); + return slot >= WritingDeskBlockEntity.COLLECTIONS + || slot >= firstVisibleTab && slot < firstVisibleTab + VISIBLE_TABS; + } + @Override public boolean mayPickup(Player player) { return isActive(); } + @Override public boolean mayPlace(ItemStack stack) { return isActive() && container.canPlaceItem(getContainerSlot(), stack); } + @Override public int getMaxStackSize() { return limit; } + } +} diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/network/PortBookNetworking.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/network/PortBookNetworking.java index 912a499..4f239a6 100644 --- a/ports/mc263/src/main/java/net/dnorberg/rivencraft/network/PortBookNetworking.java +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/network/PortBookNetworking.java @@ -33,6 +33,7 @@ public final class PortBookNetworking { public static void registerPayloads(RegisterPayloadHandlersEvent event) { var registrar = event.registrar("1"); + registrar.playToClient(WritingDeskCatalogPayload.TYPE, WritingDeskCatalogPayload.STREAM_CODEC); registrar.playToServer(BindLinkingBookPayload.TYPE, BindLinkingBookPayload.STREAM_CODEC, (payload, context) -> { if (context.player() instanceof ServerPlayer player) bind(player, payload); @@ -82,7 +83,8 @@ public final class PortBookNetworking { private static void use(ServerPlayer player, UseLinkingBookPayload payload) { ItemStack stack = player.getItemInHand(hand(payload.offhand())); if (!stack.is(Rivencraft.LINKING_BOOK.get())) return; - String failure = LinkingBookItem.travel(player, stack); + String failure = net.dnorberg.rivencraft.item.PortHandheldTravel.travel(player, + hand(payload.offhand()), stack, () -> LinkingBookItem.travel(player, stack)); if (failure != null) player.sendOverlayMessage(Component.literal(failure)); } @@ -168,13 +170,19 @@ public final class PortBookNetworking { Identifier id = stack.get(Rivencraft.AGE_BOOK_ID.get()); if (id == null) return; ServerLevel source = player.level(); + LinkBookTarget returnTarget = LinkBookTarget.at(player); Vec3 departure = player.position(); Vec3 incoming = player.getDeltaMovement(); - String failure = PortAgeTravel.enter(player, id); + String failure = net.dnorberg.rivencraft.item.PortHandheldTravel.travel(player, + hand(payload.offhand()), stack, () -> { + String result = PortAgeTravel.enter(player, id); + if (result == null) LinkingBookItem.applySuccessfulLink(player, + stack.getOrDefault(Rivencraft.LINK_PROPERTIES.get(), LinkProperties.NONE), + source, departure, incoming, player.getYRot()); + return result; + }); if (failure != null) player.sendOverlayMessage(Component.literal(failure)); - else LinkingBookItem.applySuccessfulLink(player, - stack.getOrDefault(Rivencraft.LINK_PROPERTIES.get(), LinkProperties.NONE), - source, departure, incoming, player.getYRot()); + else net.dnorberg.rivencraft.age.PortReturnLinks.afterSuccessfulTravel(player, id, returnTarget); } private static InteractionHand hand(boolean offhand) { diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/network/WritingDeskCatalogPayload.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/network/WritingDeskCatalogPayload.java new file mode 100644 index 0000000..6cf77dc --- /dev/null +++ b/ports/mc263/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.Identifier; + +/** 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<>( + Identifier.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/ports/mc263/src/main/java/net/dnorberg/rivencraft/registry/ModCreativeTabs.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/registry/ModCreativeTabs.java index fd17698..4730094 100644 --- a/ports/mc263/src/main/java/net/dnorberg/rivencraft/registry/ModCreativeTabs.java +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/registry/ModCreativeTabs.java @@ -32,6 +32,11 @@ public final class ModCreativeTabs { output.accept(Rivencraft.INK_VIAL.get()); output.accept(Rivencraft.BOOK_BINDER_ITEM.get()); output.accept(Rivencraft.INK_MIXER_ITEM.get()); + output.accept(Rivencraft.WRITING_DESK_ITEM.get()); + output.accept(Rivencraft.NOTEBOOK.get()); + output.accept(Rivencraft.COLLATION_FOLDER.get()); + output.accept(Rivencraft.SEALED_NOTEBOOK.get()); + creativeSymbolPortfolios(parameters.holders()).forEach(output::accept); }) .build()); TABS.register("rivencraft_link_pages", () -> CreativeModeTab.builder() @@ -65,6 +70,15 @@ public final class ModCreativeTabs { private ModCreativeTabs() { } + public static java.util.List creativeSymbolPortfolios( + net.minecraft.core.HolderLookup.Provider registries) { + var portfolios = new java.util.ArrayList(); + for (String category : PortSymbolPages.categories()) portfolios.addAll( + net.dnorberg.rivencraft.item.SymbolPortfolioItem.createVolumes( + net.minecraft.resources.Identifier.parse(category), PortSymbolPages.pages(category, registries))); + return java.util.List.copyOf(portfolios); + } + public static void register(IEventBus modEventBus) { TABS.register(modEventBus); } diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/NotebookContents.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/NotebookContents.java new file mode 100644 index 0000000..87bdf07 --- /dev/null +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/NotebookContents.java @@ -0,0 +1,90 @@ +package net.dnorberg.rivencraft.writing; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import java.util.ArrayList; +import java.util.List; +import net.dnorberg.rivencraft.Rivencraft; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.world.item.ItemStack; + +/** A bounded, position-preserving collection of Page stacks stored on a Notebook item. */ +public record NotebookContents(List pages) { + // Legacy did not impose a book-page cap. Our fixed-slot binder persists slot IDs + // as unsigned bytes; 252 page positions (after its three input slots) fit safely. + public static final int MAX_PAGES = 252; + public static final NotebookContents EMPTY = new NotebookContents(List.of()); + public static final Codec CODEC = ItemStack.OPTIONAL_CODEC.listOf() + .validate(NotebookContents::validate) + .xmap(NotebookContents::new, NotebookContents::pages); + public static final StreamCodec STREAM_CODEC = StreamCodec.of( + NotebookContents::encode, NotebookContents::decode); + + public NotebookContents { + if (pages.size() > MAX_PAGES) { + throw new IllegalArgumentException("Notebook exceeds " + MAX_PAGES + " page positions"); + } + List copies = new ArrayList<>(pages.size()); + for (ItemStack page : pages) { + validatePage(page).getOrThrow(); + copies.add(page.copy()); + } + pages = List.copyOf(copies); + } + + public int pageCount() { + return (int)pages.stream().filter(page -> !page.isEmpty()).count(); + } + + public int unresolvedCount() { + return (int)pages.stream() + .filter(page -> !page.isEmpty() + && page.has(Rivencraft.SYMBOL_PAGE_ID.get()) + && PortSymbolPages.find(page.get(Rivencraft.SYMBOL_PAGE_ID.get())) == null) + .count(); + } + + private static DataResult> validate(List pages) { + if (pages.size() > MAX_PAGES) { + return DataResult.error(() -> "Notebook exceeds " + MAX_PAGES + " page positions"); + } + for (ItemStack page : pages) { + DataResult result = validatePage(page); + if (result.error().isPresent()) { + return DataResult.error(() -> result.error().orElseThrow().message()); + } + } + return DataResult.success(pages); + } + + private static DataResult validatePage(ItemStack page) { + if (page.isEmpty()) { + return DataResult.success(page); + } + if (!page.is(Rivencraft.PAGE.get())) { + return DataResult.error(() -> "Notebook contains a non-Page item"); + } + return page.getCount() == 1 + ? DataResult.success(page) + : DataResult.error(() -> "Notebook Page stacks must contain exactly one item"); + } + + private static void encode(RegistryFriendlyByteBuf buffer, NotebookContents contents) { + ByteBufCodecs.VAR_INT.encode(buffer, contents.pages.size()); + contents.pages.forEach(page -> ItemStack.OPTIONAL_STREAM_CODEC.encode(buffer, page)); + } + + private static NotebookContents decode(RegistryFriendlyByteBuf buffer) { + int size = ByteBufCodecs.VAR_INT.decode(buffer); + if (size < 0 || size > MAX_PAGES) { + throw new IllegalArgumentException("Invalid Notebook page count " + size); + } + List pages = new ArrayList<>(size); + for (int index = 0; index < size; index++) { + pages.add(ItemStack.OPTIONAL_STREAM_CODEC.decode(buffer)); + } + return new NotebookContents(pages); + } +} diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/PageContent.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/PageContent.java new file mode 100644 index 0000000..4fd77b1 --- /dev/null +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/PageContent.java @@ -0,0 +1,95 @@ +package net.dnorberg.rivencraft.writing; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import io.netty.buffer.ByteBuf; +import java.util.Locale; +import java.util.Objects; +import java.util.Optional; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; + +/** Persistent contents of a Page item. Unknown symbols remain explicit and recoverable. */ +public record PageContent(Kind kind, Optional symbol) { + public static final PageContent BLANK = new PageContent(Kind.BLANK, Optional.empty()); + public static final PageContent LINK_PANEL = new PageContent(Kind.LINK_PANEL, Optional.empty()); + private static final Codec KIND_CODEC = Codec.STRING.comapFlatMap( + value -> { + try { + return DataResult.success(Kind.valueOf(value.toUpperCase(Locale.ROOT))); + } catch (IllegalArgumentException exception) { + return DataResult.error(() -> "Unknown page content kind: " + value); + } + }, + value -> value.name().toLowerCase(Locale.ROOT) + ); + private static final Codec RECORD_CODEC = RecordCodecBuilder.create(instance -> instance.group( + KIND_CODEC.fieldOf("kind").forGetter(PageContent::kind), + SymbolReference.CODEC.optionalFieldOf("symbol").forGetter(PageContent::symbol) + ).apply(instance, PageContent::new)); + public static final Codec CODEC = RECORD_CODEC.flatXmap(PageContent::validate, PageContent::validate); + public static final StreamCodec STREAM_CODEC = StreamCodec.of( + PageContent::encode, PageContent::decode); + + public PageContent { + Objects.requireNonNull(kind, "kind"); + symbol = Objects.requireNonNull(symbol, "symbol"); + boolean needsSymbol = kind == Kind.SYMBOL || kind == Kind.UNRESOLVED_SYMBOL; + if (needsSymbol != symbol.isPresent()) { + throw new IllegalArgumentException("Page contents do not match kind " + kind); + } + } + + public static PageContent symbol(SymbolReference symbol) { + return new PageContent(Kind.SYMBOL, Optional.of(symbol)); + } + + public static PageContent unresolved(SymbolReference symbol) { + return new PageContent(Kind.UNRESOLVED_SYMBOL, Optional.of(symbol)); + } + + /** Re-resolves persisted data against the authoritative current server catalog. */ + public PageContent resolve() { + if (kind != Kind.SYMBOL && kind != Kind.UNRESOLVED_SYMBOL) { + return this; + } + SymbolReference reference = symbol.orElseThrow(); + return PortSymbolPages.find(reference.id()) != null ? symbol(reference) : unresolved(reference); + } + + public boolean isResolvedSymbol() { + return kind == Kind.SYMBOL; + } + + private static DataResult validate(PageContent content) { + boolean needsSymbol = content.kind == Kind.SYMBOL || content.kind == Kind.UNRESOLVED_SYMBOL; + return needsSymbol == content.symbol.isPresent() + ? DataResult.success(content) + : DataResult.error(() -> "Page contents do not match kind " + content.kind); + } + + private static void encode(ByteBuf buffer, PageContent content) { + ByteBufCodecs.VAR_INT.encode(buffer, content.kind.ordinal()); + content.symbol.ifPresent(reference -> SymbolReference.STREAM_CODEC.encode(buffer, reference)); + } + + private static PageContent decode(ByteBuf buffer) { + int ordinal = ByteBufCodecs.VAR_INT.decode(buffer); + if (ordinal < 0 || ordinal >= Kind.values().length) { + throw new IllegalArgumentException("Unknown page content kind ordinal " + ordinal); + } + Kind kind = Kind.values()[ordinal]; + Optional symbol = kind == Kind.SYMBOL || kind == Kind.UNRESOLVED_SYMBOL + ? Optional.of(SymbolReference.STREAM_CODEC.decode(buffer)) + : Optional.empty(); + return new PageContent(kind, symbol); + } + + public enum Kind { + BLANK, + SYMBOL, + UNRESOLVED_SYMBOL, + LINK_PANEL + } +} diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/PortRegistryPages.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/PortRegistryPages.java index 14ea07b..c687e7c 100644 --- a/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/PortRegistryPages.java +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/PortRegistryPages.java @@ -103,6 +103,7 @@ public final class PortRegistryPages { private static String materialCategory(Block block) { if (block == null || block == Blocks.AIR || block == Blocks.CAVE_AIR || block == Blocks.VOID_AIR + || block == Rivencraft.CLOUD_PLATFORM.get() || block instanceof EntityBlock || block.defaultBlockState().hasBlockEntity()) return null; var state = block.defaultBlockState(); var fluid = state.getFluidState(); diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/PortSymbolNumerals.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/PortSymbolNumerals.java index 7449d60..0926a92 100644 --- a/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/PortSymbolNumerals.java +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/PortSymbolNumerals.java @@ -4,6 +4,17 @@ package net.dnorberg.rivencraft.writing; public final class PortSymbolNumerals { private PortSymbolNumerals() {} + public static int[] digitsFor(int number) { + if (number < 0) throw new IllegalArgumentException("Numeral must be non-negative"); + if (number < 25) return new int[] {number}; + int count = 0; + for (int remaining = number; remaining > 0; remaining /= 25) count++; + int[] digits = new int[count]; + for (int remaining = number, index = count - 1; index >= 0; index--, remaining /= 25) + digits[index] = remaining % 25; + return digits; + } + public static int[] componentsForDigit(int digit) { if (digit < 0 || digit >= 25) throw new IllegalArgumentException("Base-25 digit must be in [0, 24]"); if (digit == 0) return new int[] {1}; diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/PortSymbolPages.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/PortSymbolPages.java index da7ef80..d30a6a6 100644 --- a/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/PortSymbolPages.java +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/PortSymbolPages.java @@ -37,6 +37,15 @@ public final class PortSymbolPages { return List.copyOf(categories); } + /** Same bundled, non-biome/material/non-corrupted pool as 1.21.1's booster. */ + public static List sealedCandidateIds() { + return ENTRIES.values().stream().filter(entry -> entry.id().getNamespace().equals(Rivencraft.MOD_ID) + && !entry.id().getPath().startsWith("block/") + && !entry.id().getPath().startsWith("biome/") + && !entry.category().equals("rivencraft:corrupted")) + .map(Entry::id).sorted().toList(); + } + public static List pages(String category, HolderLookup.Provider registries) { List pages = new ArrayList<>(); ENTRIES.values().stream().filter(entry -> entry.category().equals(category)) diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/SymbolDisplayMetadata.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/SymbolDisplayMetadata.java new file mode 100644 index 0000000..46d4092 --- /dev/null +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/SymbolDisplayMetadata.java @@ -0,0 +1,48 @@ +package net.dnorberg.rivencraft.writing; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import io.netty.buffer.ByteBuf; +import net.dnorberg.rivencraft.Rivencraft; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.Identifier; + +/** Client-safe symbol presentation selected by the authoritative server catalog. */ +public record SymbolDisplayMetadata( + Identifier id, + String nameTranslationKey, + String descriptionTranslationKey, + Identifier category, + boolean resolved, + String poem +) { + public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( + Identifier.CODEC.fieldOf("id").forGetter(SymbolDisplayMetadata::id), + Codec.STRING.fieldOf("name").forGetter(SymbolDisplayMetadata::nameTranslationKey), + Codec.STRING.fieldOf("description").forGetter(SymbolDisplayMetadata::descriptionTranslationKey), + Identifier.CODEC.fieldOf("category").forGetter(SymbolDisplayMetadata::category), + Codec.BOOL.fieldOf("resolved").forGetter(SymbolDisplayMetadata::resolved), + Codec.STRING.optionalFieldOf("poem", "").forGetter(SymbolDisplayMetadata::poem) + ).apply(instance, SymbolDisplayMetadata::new)); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + Identifier.STREAM_CODEC, SymbolDisplayMetadata::id, + ByteBufCodecs.STRING_UTF8, SymbolDisplayMetadata::nameTranslationKey, + ByteBufCodecs.STRING_UTF8, SymbolDisplayMetadata::descriptionTranslationKey, + Identifier.STREAM_CODEC, SymbolDisplayMetadata::category, + ByteBufCodecs.BOOL, SymbolDisplayMetadata::resolved, + ByteBufCodecs.STRING_UTF8, SymbolDisplayMetadata::poem, + SymbolDisplayMetadata::new + ); + + public static SymbolDisplayMetadata unresolved(SymbolReference reference) { + return new SymbolDisplayMetadata( + reference.id(), + "symbol.rivencraft.unresolved", + "symbol.rivencraft.unresolved.description", + Identifier.fromNamespaceAndPath(Rivencraft.MOD_ID, "unresolved"), + false, + "" + ); + } +} diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/SymbolReference.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/SymbolReference.java new file mode 100644 index 0000000..0418062 --- /dev/null +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/SymbolReference.java @@ -0,0 +1,24 @@ +package net.dnorberg.rivencraft.writing; + +import com.mojang.serialization.Codec; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.Identifier; + +/** Stable namespaced identity for one writing symbol. */ +public record SymbolReference(Identifier id) { + public static final Codec CODEC = Identifier.CODEC.xmap( + SymbolReference::new, SymbolReference::id); + public static final StreamCodec STREAM_CODEC = Identifier.STREAM_CODEC.map( + SymbolReference::new, SymbolReference::id); + + public SymbolReference { + Objects.requireNonNull(id, "id"); + } + + /** Remains useful when the defining data pack has been removed. */ + public String unresolvedName() { + return id.toString(); + } +} diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/WritingDeskCatalog.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/WritingDeskCatalog.java new file mode 100644 index 0000000..a3ac0f0 --- /dev/null +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/WritingDeskCatalog.java @@ -0,0 +1,64 @@ +package net.dnorberg.rivencraft.writing; + +import net.dnorberg.rivencraft.Rivencraft; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import net.dnorberg.rivencraft.block.entity.WritingDeskBlockEntity; +import net.dnorberg.rivencraft.item.PageItem; +import net.dnorberg.rivencraft.item.CollationFolderItem; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.resources.Identifier; +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 HashMap<>(); + for (int slot = 0; slot < WritingDeskBlockEntity.COLLECTIONS; slot++) { + ItemStack collection = desk.getItem(slot); + if (collection.is(Rivencraft.PAGE.get())) add(unique, collection); + if (CollationFolderItem.isCollection(collection)) { + List pages = collection.getOrDefault( + Rivencraft.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(Rivencraft.PAGE.get())) return; + PageContent content = PageItem.content(page).resolve(); + if (content.kind() != PageContent.Kind.SYMBOL) return; + SymbolReference reference = content.symbol().orElseThrow(); + var entry = PortSymbolPages.find(reference.id()); + if (entry != null) unique.put(reference.id(), new SymbolDisplayMetadata(entry.id(), + entry.nameKey(), entry.descriptionKey(), Identifier.parse(entry.category()), true, entry.poem())); + } + + 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/ports/mc263/src/main/resources/assets/rivencraft/items/collation_folder.json b/ports/mc263/src/main/resources/assets/rivencraft/items/collation_folder.json new file mode 100644 index 0000000..2e819de --- /dev/null +++ b/ports/mc263/src/main/resources/assets/rivencraft/items/collation_folder.json @@ -0,0 +1 @@ +{"model":{"type":"minecraft:model","model":"rivencraft:item/collation_folder"}} diff --git a/ports/mc263/src/main/resources/assets/rivencraft/items/notebook.json b/ports/mc263/src/main/resources/assets/rivencraft/items/notebook.json new file mode 100644 index 0000000..55590e4 --- /dev/null +++ b/ports/mc263/src/main/resources/assets/rivencraft/items/notebook.json @@ -0,0 +1 @@ +{"model":{"type":"minecraft:model","model":"rivencraft:item/notebook"}} diff --git a/ports/mc263/src/main/resources/assets/rivencraft/items/sealed_notebook.json b/ports/mc263/src/main/resources/assets/rivencraft/items/sealed_notebook.json new file mode 100644 index 0000000..d04d503 --- /dev/null +++ b/ports/mc263/src/main/resources/assets/rivencraft/items/sealed_notebook.json @@ -0,0 +1 @@ +{"model":{"type":"minecraft:model","model":"rivencraft:item/sealed_notebook"}} diff --git a/ports/mc263/src/main/resources/assets/rivencraft/items/symbol_portfolio.json b/ports/mc263/src/main/resources/assets/rivencraft/items/symbol_portfolio.json new file mode 100644 index 0000000..318a8e5 --- /dev/null +++ b/ports/mc263/src/main/resources/assets/rivencraft/items/symbol_portfolio.json @@ -0,0 +1 @@ +{"model":{"type":"minecraft:model","model":"rivencraft:item/symbol_portfolio"}} diff --git a/ports/mc263/src/main/resources/assets/rivencraft/items/writing_desk.json b/ports/mc263/src/main/resources/assets/rivencraft/items/writing_desk.json new file mode 100644 index 0000000..761547e --- /dev/null +++ b/ports/mc263/src/main/resources/assets/rivencraft/items/writing_desk.json @@ -0,0 +1 @@ +{"model":{"type":"minecraft:model","model":"rivencraft:item/writing_desk"}} diff --git a/ports/mc263/src/main/resources/data/rivencraft/advancement/recipes/misc/notebook.json b/ports/mc263/src/main/resources/data/rivencraft/advancement/recipes/misc/notebook.json new file mode 100644 index 0000000..a1d33af --- /dev/null +++ b/ports/mc263/src/main/resources/data/rivencraft/advancement/recipes/misc/notebook.json @@ -0,0 +1,9 @@ +{ + "parent": "minecraft:recipes/root", + "criteria": { + "has_material": {"trigger":"minecraft:inventory_changed","conditions":{"items":[{"items":"minecraft:leather"}]}}, + "has_the_recipe": {"trigger":"minecraft:recipe_unlocked","conditions":{"recipes":["rivencraft:notebook"]}} + }, + "requirements": [["has_material", "has_the_recipe"]], + "rewards": {"recipes":["rivencraft:notebook"]} +} diff --git a/ports/mc263/src/main/resources/data/rivencraft/advancement/recipes/misc/writing_desk.json b/ports/mc263/src/main/resources/data/rivencraft/advancement/recipes/misc/writing_desk.json new file mode 100644 index 0000000..cea8165 --- /dev/null +++ b/ports/mc263/src/main/resources/data/rivencraft/advancement/recipes/misc/writing_desk.json @@ -0,0 +1,9 @@ +{ + "parent":"minecraft:recipes/root", + "criteria":{ + "has_material":{"trigger":"minecraft:inventory_changed","conditions":{"items":[{"items":"minecraft:feather"}]}}, + "has_the_recipe":{"trigger":"minecraft:recipe_unlocked","conditions":{"recipes":["rivencraft:writing_desk"]}} + }, + "requirements":[["has_material","has_the_recipe"]], + "rewards":{"recipes":["rivencraft:writing_desk"]} +} diff --git a/ports/mc263/src/main/resources/data/rivencraft/recipe/notebook.json b/ports/mc263/src/main/resources/data/rivencraft/recipe/notebook.json new file mode 100644 index 0000000..ab1fe11 --- /dev/null +++ b/ports/mc263/src/main/resources/data/rivencraft/recipe/notebook.json @@ -0,0 +1,7 @@ +{ + "type": "minecraft:crafting_shaped", + "category": "misc", + "key": {"L": "minecraft:leather", "S": "minecraft:string"}, + "pattern": [" L ", "S ", " L "], + "result": {"count": 1, "id": "rivencraft:notebook"} +} diff --git a/ports/mc263/src/main/resources/data/rivencraft/recipe/writing_desk.json b/ports/mc263/src/main/resources/data/rivencraft/recipe/writing_desk.json new file mode 100644 index 0000000..575726c --- /dev/null +++ b/ports/mc263/src/main/resources/data/rivencraft/recipe/writing_desk.json @@ -0,0 +1,6 @@ +{ + "type":"minecraft:crafting_shaped", "category":"misc", + "pattern":["V F", "PPP", "P P"], + "key":{"V":"minecraft:glass_bottle", "F":"minecraft:feather", "P":"#minecraft:planks"}, + "result":{"id":"rivencraft:writing_desk", "count":1} +}