Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c08f391a2 | ||
|
|
b8d26a9036 | ||
|
|
d0af75239a | ||
|
|
2e5d6e0427 | ||
|
|
54c10cca6c | ||
|
|
c39ebd78ba | ||
|
|
1ff62e9aaa |
No files matched your search
@@ -198,7 +198,7 @@ Linking creates a bound Return Link to the exact source position and orientation
|
||||
according to the configured Return Link policy.
|
||||
|
||||
Server owners can set `descriptive_books.return_link_mode` in Rivencraft's
|
||||
server config to `ON_CREATION` (default), `ALWAYS`, or `DISABLED`.
|
||||
server config to `DISABLED` (default), `ON_CREATION`, or `ALWAYS`.
|
||||
Age creation, persistence, lifecycle commands, configuration, backups, and
|
||||
administrator recovery are documented in [docs/AGES.md](docs/AGES.md).
|
||||
Data-pack runtime profiles are documented in
|
||||
@@ -208,7 +208,7 @@ Data-pack runtime profiles are documented in
|
||||
|
||||
- A Java 21 JDK
|
||||
- Minecraft 1.21.1
|
||||
- NeoForge 21.1.251 or newer in the 21.1 line
|
||||
- NeoForge 21.1.264 or newer in the 21.1 line
|
||||
- Dynamic Dimensions AR Edition 0.9.1 and Bad Packets 0.8.2 (client and server)
|
||||
|
||||
Create is optional. When installed, Rivencraft uses its Ponder renderer for
|
||||
|
||||
@@ -132,6 +132,8 @@ neoForge {
|
||||
|
||||
server {
|
||||
server()
|
||||
// Keep dedicated compatibility runs away from the normal dev world.
|
||||
gameDirectory = project.file(project.findProperty('serverRunDir') ?: 'run')
|
||||
programArgument '--nogui'
|
||||
systemProperty 'neoforge.enabledGameTestNamespaces', project.mod_id
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 319 KiB After Width: | Height: | Size: 11 KiB |
@@ -69,10 +69,9 @@ definition.
|
||||
Rivencraft writes gameplay settings to the world/server configuration managed
|
||||
by NeoForge. `descriptive_books.return_link_mode` accepts:
|
||||
|
||||
- `ON_CREATION` (default): create one Return Link the first time a player
|
||||
enters each Age.
|
||||
- `DISABLED` (default): never create Return Links automatically.
|
||||
- `ON_CREATION`: create one Return Link the first time a player enters each Age.
|
||||
- `ALWAYS`: create a Return Link after every successful Descriptive Book trip.
|
||||
- `DISABLED`: never create Return Links automatically.
|
||||
|
||||
Each client has a separate `rivencraft-client.toml`. Its `book_preview.mode`
|
||||
setting accepts `AUTO`, `IMMERSIVE_PORTALS`, `PONDER`, or `LEGACY`. This changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Age arrival cloud platform
|
||||
# Age arrival platform
|
||||
|
||||
Newly created Ages now place a 5×5 solid cloud platform instead of cobblestone at their saved spawn. The block has full collision, passes skylight, renders translucently, has no loot table, and cannot be obtained with pick-block. Existing Ages retain their already-placed platform; this change does not overwrite a player's saved world.
|
||||
Newly created Ages place a 5×5 solid platform instead of cobblestone at their saved spawn. The block has full collision, renders as an opaque full block, breaks as easily as glass (0.3 hardness), has no loot table, and cannot be obtained with pick-block. Existing Ages retain their already-placed platform blocks, which adopt the new appearance and hardness without altering saved terrain.
|
||||
|
||||
The block texture is `assets/rivencraft/textures/block/cloud_platform.png`, verified at exactly 16×16 RGBA. Legacy's supplied assets contain no cloud-block texture, so this one was generated with the built-in image tool, then reduced by nearest-neighbor sampling. The final prompt requested a seamless Minecraft block-face pixel-art tile with 2–3 chunky white, icy-blue, and powder-blue cloud swirls, genuine transparent holes between them, strong 16×16-scale contrast, and no background or text.
|
||||
The model reuses `assets/rivencraft/textures/block/crystal_block.png`, the 16×16 Legacy crystal tile. The former generated cloud texture is removed. The internal block ID remains `rivencraft:cloud_platform` to preserve saved worlds.
|
||||
|
||||
The separate [decay provider](INSTABILITY_DECAY.md) protects the saved spawn area by position, so its protection remains in force regardless of the platform's material.
|
||||
@@ -19,12 +19,12 @@ project's GNU LGPL v3 license. Rivencraft itself uses the same license.
|
||||
| `assets/rivencraft/textures/block/book_binder_press.png` | `assets/mystcraft/textures/entity/bookbinder/press_part.png` | `9bc8ddc061845df0cd5ea47f4fdef773cff786ae` | Namespace/path rename only; Book Binder model uses this press texture |
|
||||
| `assets/rivencraft/textures/gui/book_binder_legacy.png` | `assets/mystcraft/gui/pagebinder.png` | `9bc8ddc061845df0cd5ea47f4fdef773cff786ae` | Namespace/path rename only; Book Binder GUI uses the original 176×181 panel |
|
||||
| `assets/rivencraft/textures/gui/ink_mixer_legacy.png` | `assets/mystcraft/gui/inkmixer.png` | `9bc8ddc061845df0cd5ea47f4fdef773cff786ae` | Namespace/path rename only; Ink Mixer GUI uses the original 176×181 panel |
|
||||
| `assets/rivencraft/textures/block/ink_mixer_top.png`, `ink_mixer_side.png`, `ink_mixer_bottom.png` | `assets/mystcraft/textures/blocks/inkmixer_top.png`, `inkmixer_side.png`, `inkmixer_bottom.png` | `9bc8ddc061845df0cd5ea47f4fdef773cff786ae` | Namespace/path rename only; Rivencraft supplies its own block model |
|
||||
| `assets/rivencraft/textures/block/inkwell_base.png`, `inkwell_cup.png`, `ink.png`, `ink_glass.png` | `assets/mystcraft/textures/entity/inkwell/` counterparts | Legacy 0.13.7.06 local extraction | Byte-identical copies used by the ported Ink Mixer geometry; the paper overlay uses vanilla white concrete instead of Legacy's black surface |
|
||||
| `assets/rivencraft/textures/item/linking_book_unwritten.png` | `assets/mystcraft/textures/items/unlinked.png` | `9bc8ddc061845df0cd5ea47f4fdef773cff786ae` | Namespace/path rename only |
|
||||
| `assets/rivencraft/textures/item/page_link_panel.png` | `page_background.png` and `PageBuilder.BasicPageSprite` | `9bc8ddc061845df0cd5ea47f4fdef773cff786ae` | Reproduces the legacy 160 px scale, black panel at `(25,30)`–`(135,75)`, and 128 px resample |
|
||||
| `assets/rivencraft/textures/gui/symbolcomponents.png` | `assets/mystcraft/textures/symbolcomponents.png` | `9bc8ddc061845df0cd5ea47f4fdef773cff786ae` | Byte-identical shared 512×512 component sheet for client-side page composition |
|
||||
| Runtime-composed symbol pages (`SymbolPageTextures`) | `page_background.png`, `textures/symbolcomponents.png`, `WordData`, `ModSymbols`, and `PageBuilder.PageSprite` | `9bc8ddc061845df0cd5ea47f4fdef773cff786ae` | Replaces the six pre-generated symbol PNGs; layers four poem words on the shared page background and caches bounded client textures |
|
||||
| `assets/rivencraft/textures/block/crystal_block.png` | `assets/mystcraft/textures/blocks/crystal.png` | Legacy 0.13.7.06 local extraction | Exact 16×16 blue tile shared by Crystal Block and Budding Crystal models after removing a byte-identical duplicate |
|
||||
| `assets/rivencraft/textures/block/crystal_block.png` | `assets/mystcraft/textures/blocks/crystal.png` | Legacy 0.13.7.06 local extraction | Exact 16×16 blue tile shared by Crystal Block, Budding Crystal, and the non-dropping Age arrival platform; its former cloud sprite is removed |
|
||||
| `assets/rivencraft/textures/block/{small,medium,large}_crystal_bud.png`, `crystal_cluster.png` | Minecraft 1.21.1 `textures/block/{small,medium,large}_amethyst_bud.png`, `amethyst_cluster.png` | Minecraft 1.21.1 | Original 16×16 alpha masks and silhouettes preserved; hue shifted toward Legacy blue, with brighter saturation; exported as 8-bit RGBA (not indexed PNG) |
|
||||
| `assets/rivencraft/textures/item/crystal_shard.png` | Original sprite generated with OpenAI ImageGen from the Legacy crystal palette | 2026-10-05 | Cropped and nearest-neighbor scaled to 16×16 |
|
||||
| `assets/rivencraft/sounds/linking/link-fissure.ogg` | `assets/mystcraft/sounds/linking/link-fissure.ogg` | Legacy 0.13.7.06 local extraction | Namespace/path rename only; plays when a Star Fissure returns an entity home |
|
||||
@@ -32,6 +32,8 @@ project's GNU LGPL v3 license. Rivencraft itself uses the same license.
|
||||
| `assets/rivencraft/textures/block/book_receptacle.png` | `assets/mystcraft/textures/blocks/book_receptacle.png` | Legacy 0.13.7.06 local extraction | Byte-identical 16×16 face tile; modern block model recreates the Legacy thin slab |
|
||||
| `assets/rivencraft/textures/block/crystal_portal.png` and `.mcmeta` | `assets/mystcraft/textures/blocks/portal.png` and `.mcmeta` | Legacy 0.13.7.06 local extraction | Byte-identical texture and line-ending-normalized animation metadata, used when Immersive Portals is absent |
|
||||
| `assets/rivencraft/sounds/linking/link-portal.ogg` | `assets/mystcraft/sounds/linking/link-portal.ogg` | Legacy 0.13.7.06 local extraction | Byte-identical portal-link sound |
|
||||
| `assets/rivencraft/sounds/linking/link-disarm.ogg` | `assets/mystcraft/sounds/linking/link-disarm.ogg` | Legacy 0.13.7.06 local extraction | Byte-identical disarm-link sound; takes precedence over the portal sound as in Legacy |
|
||||
| `assets/rivencraft/sounds/linking/link-intra.ogg` | `assets/mystcraft/sounds/linking/link-intra.ogg` | Legacy 0.13.7.06 local extraction | Byte-identical intra-link sound for Linking Books |
|
||||
|
||||
Mystcraft Legacy identifies XCompWiz as its project developer and includes the
|
||||
full LGPL v3 text in its root `LICENSE` file. Rivencraft's root `LICENSE`
|
||||
|
||||
@@ -46,7 +46,7 @@ For a manual check, create a fresh surface Age and run `/rivencraft library near
|
||||
|
||||
Issue #75's comment calls this a despawning drop. The code differs subtly: [`EntityLinkbook`](https://github.com/Mystcraft/Mystcraft-Legacy/blob/9bc8ddc061845df0cd5ea47f4fdef773cff786ae/src/main/java/com/xcompwiz/mystcraft/entity/EntityLinkbook.java#L190-L224) explicitly cannot despawn, but takes one starvation damage every 10,000 ticks and damage while wet. It can eventually be destroyed if left unattended; a vanilla item entity's fixed five-minute timeout is not parity. A modern equivalent should retain the visible book and avoid silently deleting its Age identity at that timeout.
|
||||
|
||||
Legacy's travel path creates no automatic Return Link. Rivencraft currently defaults to `ON_CREATION` via `DescriptiveBookItem` and `descriptive_books.return_link_mode`. Per #75's comment, change the **new-config default** to `DISABLED` while retaining `ON_CREATION` and `ALWAYS` as opt-ins. Existing saved server configs must keep their explicit value. Handheld-book drop and Return Link default are tracked in [#85](https://truenas-scale.tail180fb1.ts.net:8443/dnorberg/rivencraft/issues/85), not portal-frame logic.
|
||||
Legacy's travel path creates no automatic Return Link. Rivencraft now defaults to `DISABLED` in new server configs while retaining `ON_CREATION` and `ALWAYS` as opt-ins. Existing saved server configs keep their explicit value. Issue #85 adds the active-book drop only to hand-held Linking and Descriptive Book travel. On a successful link, the exact active stack becomes a dedicated `DroppedBookEntity` at the departure point, rendered with the same open `BookModel` and textures as the book stand; a rejected link restores it to the original hand. Stand and portal use leave their books in place. The entity has unlimited vanilla lifetime and loses one of its five item-health points every 10,000 loaded ticks, plus one per wet tick, matching Legacy's slow/wet decay. Unlike Legacy's custom entity, the modern book is picked up by ordinary contact, not sneaking with an empty hand, and remains subject to ordinary item hazards. Following is not yet a Rivencraft modifier, so no Following exception is exposed or silently simulated. Both hands can activate a book, whereas Legacy's explicit stack removal tested only its selected main-hand slot.
|
||||
|
||||
## Crafting, sounds, and progression
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# Milestone 7 advancements and discovery (#81)
|
||||
|
||||
Rivencraft's advancement tab follows the visible progression in Mystcraft
|
||||
Legacy's `assets/mystcraft/advancements/`: Page, unwritten Linking Book,
|
||||
Writing Desk writing, Age Book, and safe Age travel. Titles such as *The Ending
|
||||
Has Not Yet Been Written*, *D'ni Ink*, and *The Art of Writing* reference Myst
|
||||
without requiring any assets from that game. The Legacy hidden Quinn trigger
|
||||
has no established Rivencraft action and is deliberately omitted.
|
||||
|
||||
`root`, `page`, and `linking_book` use vanilla server-side tick/inventory
|
||||
criteria. `link_panel`, `copy_page`, `bind_age`, `open_portal`, and `enter_age`
|
||||
use an impossible criterion awarded only after the server commits the
|
||||
corresponding action. The Age-travel award requires a successful book or portal
|
||||
link ending in a registered Rivencraft Age. Opening a book GUI, rendering a
|
||||
destination preview, viewing a recipe, or merely loading an Age cannot award
|
||||
it. Progress is stored by Minecraft per player and survives world restarts.
|
||||
|
||||
Vanilla recipe-book unlock advancements now cover the Writing Desk, Ink Mixer,
|
||||
Book Binder, and Notebook, in addition to the existing Ink Vial, Crystal
|
||||
Block, and Book Receptacle unlocks. The component-sensitive Linking Book
|
||||
crafting recipe remains a special recipe: its Link Panel requirement and the
|
||||
Writing Desk/Ink Mixer/Book Binder processes are explained by Rivencraft's
|
||||
optional JEI integration. No recipe or advancement requires JEI, Create, or
|
||||
Immersive Portals to be installed.
|
||||
|
||||
Manual Survival check: collect a Page and Linking Book; use the Ink Mixer to
|
||||
take a Link Panel; copy a Page at the Writing Desk; bind a Descriptive Book;
|
||||
enter its Age; activate a Crystal Portal. Confirm each advancement appears
|
||||
only after the completed action, not from an output preview or GUI opening.
|
||||
Repeat the actions with two players to confirm only the actor receives each
|
||||
award. Restart an existing save and confirm completed progress remains, then
|
||||
complete one previously unearned milestone. With JEI absent, the vanilla
|
||||
recipe book should still reveal standard crafting recipes as their ingredients
|
||||
are discovered; with JEI installed, the special writing guides should appear.
|
||||
|
||||
The automated Book Binder output-award scenario stalls in the GameTest storage
|
||||
runner, so it is intentionally omitted under the project test policy. For its
|
||||
manual check, open a ready Binder, observe that merely previewing the finished
|
||||
book does not award *A Book of Ages*, then take the book and confirm the award.
|
||||
Repeat by shift-clicking the result in a fresh Binder.
|
||||
@@ -0,0 +1,53 @@
|
||||
# Milestone 7 compatibility review (#82)
|
||||
|
||||
Reviewed 2026-10-19 for Minecraft 1.21.1. The project now compiles against
|
||||
NeoForge 21.1.264 and advertises that as its minimum. The official
|
||||
[NeoForge Maven metadata](https://maven.neoforged.net/releases/net/neoforged/neoforge/maven-metadata.xml)
|
||||
lists 21.1.264 as the newest 21.1 release. Its
|
||||
[changelog](https://maven.neoforged.net/releases/net/neoforged/neoforge/21.1.264/neoforge-21.1.264-changelog.txt)
|
||||
includes received split-packet size checks (21.1.260), network setup payload
|
||||
direction/decoding limits (21.1.261), ad-hoc channel identifier limits
|
||||
(21.1.262), and an initial attachment-sync fix (21.1.264). Those are concrete
|
||||
reasons to evaluate an upgrade, but do not by themselves prove Rivencraft and
|
||||
its optional integrations work on that loader.
|
||||
|
||||
**Decision: adopt 21.1.264 beginning with 0.6.6.2.** An override build
|
||||
succeeded, but its GameTest server repeated its storage-progress display
|
||||
without producing test results. That run was stopped; it is neither a pass nor
|
||||
evidence of a NeoForge defect. The player subsequently passed an in-game
|
||||
singleplayer test on 21.1.264 with the optional stack, then passed a dedicated
|
||||
server test including Immersive Portals travel to a Rivencraft Age. The
|
||||
dedicated server also reached the ready state with only the required mods
|
||||
before the full optional stack was installed. Both modes used Rivencraft
|
||||
0.6.6.1. The earlier 21.1.251 baseline passed 70 required GameTests for
|
||||
release V0.6.6.0.
|
||||
|
||||
| Dependency | Project baseline | Newest checked 1.21.1 release | Follow-up |
|
||||
| --- | --- | --- | --- |
|
||||
| Dynamic Dimensions AR Edition (required) | 0.9.1 | 0.9.1 | No newer release found on [CurseForge](https://www.curseforge.com/minecraft/mc-mods/dynamicdimension-ar-edition/files/all). |
|
||||
| Bad Packets (required) | neo-0.8.2 | neo-0.8.2 | No newer matching [Modrinth](https://modrinth.com/mod/badpackets/versions) release found. |
|
||||
| JEI (optional) | 19.57.0.449 at #82 review | 19.69.0.457 beta | Updated in #86 after client and dedicated GameTest passes; see [JEI review](MILESTONE_7_JEI_REVIEW.md). |
|
||||
| Create (optional) | 6.0.10 | 6.0.10 | No newer matching [Modrinth](https://modrinth.com/mod/create/versions) release found. Its jar bundles Ponder 1.0.82. |
|
||||
| Ponder (bundled with Create) | 1.0.82 | 1.0.87 library artifact | [Maven](https://maven.createmod.net/net/createmod/ponder/ponder-neoforge/maven-metadata.xml) has a newer artifact, but Create 6.0.10 still bundles 1.0.82. Do not treat this as a separately installed Create update. |
|
||||
| Immersive Portals (optional) | 6.0.7 | 6.0.7 | No newer matching [Modrinth](https://modrinth.com/mod/immersive-portals-neoforge/versions) release found. |
|
||||
| TerraBlender (optional) | 4.1.0.8 | 4.1.0.8 | No newer matching [Modrinth](https://modrinth.com/mod/terrablender/versions) release found. |
|
||||
| Serene Seasons (optional) | 10.1.0.8 at #82 review | 10.1.0.9 | Updated in #88 after the player's full seasonal matrix passed; see [Serene review](MILESTONE_7_SERENE_REVIEW.md). |
|
||||
| GlitchCore (Serene dependency) | 2.1.0.2 | 2.1.0.2 | No newer matching [Modrinth](https://modrinth.com/mod/glitchcore/versions) release found. |
|
||||
| Biomes O' Plenty (TerraBlender test addon) | 21.1.0.14 in user's Prism instance | 21.1.0.14 | No newer matching [Modrinth](https://modrinth.com/mod/biomes-o-plenty/versions) release found. |
|
||||
|
||||
The Modrinth version comparisons above were filtered to **NeoForge and
|
||||
Minecraft 1.21.1** on 2026-10-19. Create's bundled Ponder version was read
|
||||
from `META-INF/jarjar/metadata.json` in `create-1.21.1-6.0.10.jar`; the
|
||||
separate library-artifact comparison came from Create's Maven. Dynamic
|
||||
Dimensions was checked on CurseForge. Versions for other Minecraft lines and
|
||||
similarly named mods were excluded.
|
||||
|
||||
The full dedicated test stack contained Create/Ponder, Immersive Portals, JEI,
|
||||
TerraBlender with Biomes O' Plenty, and Serene Seasons with GlitchCore. The
|
||||
server loaded a new `rivencraft:age/test`, and its shutdown log confirms that
|
||||
all Overworld, Nether, End, and Age chunks were saved. The player explicitly
|
||||
approved 21.1.264 after that test. This does not independently certify every
|
||||
optional combination, the LEGACY preview fallback on a client without Create
|
||||
and Immersive Portals, or a full restart/rejoin cycle; keep those in the
|
||||
regression checklist. Serene's GameTest mock-player network limitation still
|
||||
requires real-client checks for seasonal behavior.
|
||||
@@ -0,0 +1,41 @@
|
||||
# JEI compatibility review (#86)
|
||||
|
||||
Reviewed 2026-10-20 for Minecraft 1.21.1 and NeoForge 21.1.264. Rivencraft
|
||||
now targets JEI 19.69.0.457 after the client's in-game acceptance check. JEI
|
||||
remains a `compileOnly` API and an opt-in
|
||||
`localRuntime` dependency for development; the published mod has no required
|
||||
JEI dependency.
|
||||
|
||||
The [official 1.21.1 NeoForge files](https://www.curseforge.com/minecraft/mc-mods/jei/files/all?page=1&pageSize=20&version=1.21.1)
|
||||
list 19.69.0.457 (beta, 2026-10-20) after 19.64.0.455 (release, 2026-10-16).
|
||||
The [19.69.0.457 Modrinth changelog](https://modrinth.com/mod/jei/versions?g=1.21.1&l=neoforge)
|
||||
has a published-jar bytecode compatibility check. Its preceding 19.69.0.456
|
||||
build changes ingredient search to Lucene and adds a runtime ingredient-search
|
||||
refresh API. This makes page search a required client check, even though the
|
||||
current Rivencraft plugin still compiles against the 457 API. The new Maven
|
||||
runtime artifact is much larger (about 7 MB versus 2.3 MB for 455); a successful
|
||||
compile alone is not evidence that its runtime plugin works.
|
||||
|
||||
Checks performed before changing the pinned dependency:
|
||||
|
||||
| Configuration | Result |
|
||||
| --- | --- |
|
||||
| `./gradlew -Pjei_version=19.69.0.457 -PwithJei=true build` | Passed; existing JEI API uses compile with four deprecation warnings and no errors. |
|
||||
| `./gradlew -Pjei_version=19.69.0.457 -PwithJei=true runGameTestServer` | Passed; dedicated GameTest server loaded JEI 19.69.0.457 and all 74 required tests passed. |
|
||||
| `./gradlew -Pjei_version=19.69.0.457 runGameTestServer` | Passed without JEI; all 74 required tests passed. |
|
||||
| Prism client with JEI 19.69.0.457 and Rivencraft 0.6.6.6 | Client joined an integrated world; JEI registered Rivencraft extra ingredients and started without a JEI-plugin exception. The player reported an in-game pass for guide display, page search, and recipe visibility. |
|
||||
|
||||
On first 457 launch, JEI migrated its old `lowMemorySlowSearchEnabled` config
|
||||
key and saved a backup of the original `jei-client.ini`; that warning was
|
||||
handled by JEI and did not block startup. No Rivencraft config was changed.
|
||||
|
||||
The in-game acceptance report covers the visible JEI integration. A dedicated
|
||||
GameTest server proves no server-side startup failure but does not exercise the
|
||||
JEI GUI; keep page search, recipes, blank-Page hiding, and the Ink Mixer,
|
||||
Writing Desk, and Book Binder guides in future upgrade checks. JEI is not
|
||||
bundled into or required by the published Rivencraft jar.
|
||||
|
||||
The final pinned `0.6.6.7` build passed. A repeat GameTest run from the pinned
|
||||
configuration stalled during test-world preparation before running any cases;
|
||||
it was stopped. The two successful pre-pin runs above used the same JEI version
|
||||
and code, with the dependency supplied by a Gradle property override.
|
||||
@@ -0,0 +1,31 @@
|
||||
# Serene Seasons 10.1.0.9 compatibility review (#88)
|
||||
|
||||
Reviewed 2026-10-20 for Minecraft 1.21.1 and NeoForge 21.1.264. Rivencraft
|
||||
now compiles against [Serene Seasons 10.1.0.9](https://modrinth.com/mod/serene-seasons/version/pHEgQQUE)
|
||||
with [GlitchCore 2.1.0.2](https://modrinth.com/mod/glitchcore/version/S2TfWrZR).
|
||||
Both remain optional. The Serene update fixes a precipitation-mixin recursion;
|
||||
it does not introduce a new dynamic-dimension API.
|
||||
|
||||
The 10.1.0.8 and 10.1.0.9 bytecode for Rivencraft's version-sensitive
|
||||
`SeasonsConfig`, `SeasonHandler`, `SeasonSavedData`, and `SeasonTime` integration
|
||||
classes is identical. In particular, the dimension whitelist field, per-level
|
||||
saved season clock, and season update method have unchanged signatures and
|
||||
implementations. The adapter still writes the whitelist through GlitchCore's
|
||||
config API and sends Rivencraft's whitelist update to already connected clients.
|
||||
|
||||
Verification:
|
||||
|
||||
| Check | Result |
|
||||
| --- | --- |
|
||||
| Build against 10.1.0.9 and GlitchCore 2.1.0.2 | Passed. |
|
||||
| Real Prism client with Serene 10.1.0.9, GlitchCore 2.1.0.2, and Rivencraft 0.6.6.7 | Both mods loaded and the player joined an Age. |
|
||||
| No Seasons, normal Seasons, and all four Eternal modes in new and saved Ages | Player reported the full in-game matrix passed. |
|
||||
| Reconnect, restart, and Overworld independence | Player reported the full in-game matrix passed. |
|
||||
| Isolated dedicated-server startup without Serene/GlitchCore | Reached `Done` on port 25568 with only required runtime mods, then saved and stopped. |
|
||||
| Isolated dedicated-server startup with Serene/GlitchCore | Reached `Done` on port 25569 with 10.1.0.9/2.1.0.2, then saved and stopped. |
|
||||
|
||||
Serene's sync packet is incompatible with GameTest mock players, so its
|
||||
in-game behavior must be checked with a real client and server. The seasonal
|
||||
test path in [SERENE_SEASONS_COMPATIBILITY.md](SERENE_SEASONS_COMPATIBILITY.md)
|
||||
remains the regression checklist for future upgrades. Rivencraft does not
|
||||
bundle or require Serene Seasons or GlitchCore.
|
||||
@@ -0,0 +1,63 @@
|
||||
# Milestone 7 visual and audio polish (#80)
|
||||
|
||||
The source is the local Mystcraft 0.13.7.06 extraction under
|
||||
`/home/dnorberg/Documents/mystcraft-src/mystcraft-1.12.2-0.13.7.06/`.
|
||||
Reused textures and OGG files are recorded in [asset provenance](ASSET_PROVENANCE.md).
|
||||
The `dev images/book binder.png` and `dev images/ink mixer.png` screenshots
|
||||
are the Legacy model references used for this slice.
|
||||
|
||||
## Legacy comparison and modern boundary
|
||||
|
||||
- The Crystal Block, Book Receptacle, and 16×512 portal animation already use
|
||||
Legacy sprites. The Receptacle's 16×16×6 model already matches the Legacy
|
||||
slab. The fallback portal now uses Legacy's four-pixel-deep, double-faced
|
||||
`linkportal_ns.json` geometry, with the original animation; it declares a
|
||||
translucent render layer and portal particle texture. The block remains
|
||||
non-occluding. When Immersive Portals supplies a live view, the fallback
|
||||
model is hidden, while the crystal frame is drawn over the view.
|
||||
- The Age arrival platform now reuses the same Legacy crystal tile as Crystal
|
||||
Block. It is an opaque, occluding full block, while retaining its empty loot
|
||||
table and unobtainable pick-block behavior. It has glass-like 0.3 hardness.
|
||||
The old cloud sprite is unused and removed.
|
||||
- The Book Binder now includes the two inner braces and central press/book
|
||||
piece omitted by the earlier model, using the existing byte-identical
|
||||
Legacy textures. The Ink Mixer uses Legacy's `inkwell_model.json` geometry
|
||||
and four original inkwell textures. A thin white paper surface replaces the
|
||||
black paper appearance from the reference; the old simplified mixer tiles
|
||||
are no longer used. The half-alpha glass atlas is intentionally rendered on
|
||||
a cutout layer; its more opaque look passed the player's in-game check.
|
||||
- Legacy's link listener selects Disarm before a custom portal sound, then
|
||||
Following, Intra Linking, and ordinary Link. Rivencraft has no Following
|
||||
modifier yet, so it selects Disarm, portal, Intra Linking, or ordinary Link
|
||||
in that order. The Disarm and Intra OGGs are byte-identical Legacy copies.
|
||||
- Legacy sends 50 small neutral link particles in roughly a one-by-two-block
|
||||
region at both departure and arrival. Its fixed-function particle class is
|
||||
not portable to 1.21.1. Rivencraft uses a one-shot burst of 50 vanilla Cloud
|
||||
particles at both ends, emitted only after successful server-authoritative
|
||||
travel. There is no continuous portal-particle loop in the inspected Legacy
|
||||
`BlockLinkPortal` class, so the portal surface itself adds none.
|
||||
- The Legacy `textures/entity/linkbook.png` and `linking/pop.ogg` are inspected
|
||||
but not copied in this slice. The custom dropped Linkbook entity is part of
|
||||
#85; adding its assets before the entity exists would leave unused resources.
|
||||
Library walls, shelves, chest, and webs use vanilla blocks in the template,
|
||||
so there are no separate library art files to port.
|
||||
|
||||
## Manual visual/audio check
|
||||
|
||||
Use the same bound book in a held use and a Crystal Portal. Confirm the link
|
||||
particles and audio play at both ends, and the portal landing matches the book.
|
||||
Repeat with Disarm and Intra Linking panels to check the distinct sounds.
|
||||
Compare the fallback portal, crystal frame, and Receptacle with Immersive
|
||||
Portals absent; verify the animated surface is visible from both sides and
|
||||
does not expose caves or other geometry through solid blocks. With Immersive
|
||||
Portals installed, confirm the live view replaces only the fallback surface,
|
||||
the frame draws in front, and no fallback texture covers the live view.
|
||||
Create/Ponder is optional and must not affect either rendering path.
|
||||
Check a freshly generated arrival platform from above and below: the crystal
|
||||
tile should be opaque, break about as quickly as glass, and drop no block item.
|
||||
Compare the Book Binder press and Ink Mixer cup/glass against the `dev images`
|
||||
references; the mixer paper should appear white, and neither block should
|
||||
show missing-texture checkerboards or see-through-world artifacts.
|
||||
|
||||
The player reported that the in-game visual and audio checks pass on 2026-10-19,
|
||||
including acceptance of the cutout glass appearance and current block sounds.
|
||||
@@ -45,7 +45,7 @@ being captured in the preview.
|
||||
|
||||
## Supported combinations and upstream limits
|
||||
|
||||
The compatibility baseline is Minecraft 1.21.1, NeoForge 21.1.251, Create
|
||||
The compatibility baseline is Minecraft 1.21.1, NeoForge 21.1.264, Create
|
||||
6.0.10, and Immersive Portals 6.0.7. Core Age generation and the `LEGACY`
|
||||
backend run without Create or Immersive Portals; the headless GameTest server
|
||||
uses that dependency-free path.
|
||||
|
||||
@@ -18,11 +18,11 @@ minecraft_version=1.21.1
|
||||
# as they do not follow standard versioning conventions.
|
||||
minecraft_version_range=[1.21.1]
|
||||
# The Neo version must agree with the Minecraft version to get a valid artifact
|
||||
neo_version=21.1.251
|
||||
neo_version=21.1.264
|
||||
ponder_version=1.0.82
|
||||
dynamic_dimensions_file_id=7707789
|
||||
badpackets_version=0.8.2
|
||||
jei_version=19.57.0.449
|
||||
jei_version=19.69.0.457
|
||||
# The loader version range can only use the major version of FML as bounds
|
||||
loader_version_range=[1,)
|
||||
|
||||
@@ -36,10 +36,10 @@ mod_name=Rivencraft
|
||||
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
|
||||
mod_license=LGPL-3.0-or-later
|
||||
# The mod version. Rivencraft uses the x.y.z.b policy documented in README.md.
|
||||
mod_version=0.6.4.4
|
||||
mod_version=0.6.6.8
|
||||
immersive_portals_version=6.0.7
|
||||
terrablender_version=4.1.0.8
|
||||
serene_seasons_version_id=B6nTjh4b
|
||||
serene_seasons_version_id=pHEgQQUE
|
||||
glitchcore_version_id=S2TfWrZR
|
||||
# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository.
|
||||
# This should match the base package used for the mod sources.
|
||||
|
||||
@@ -54,6 +54,7 @@ public final class Rivencraft {
|
||||
ModBiomeSources.register(modEventBus);
|
||||
ModChunkGenerators.register(modEventBus);
|
||||
ModBlocks.register(modEventBus);
|
||||
net.dnorberg.rivencraft.registry.ModEntities.register(modEventBus);
|
||||
ModBlockEntities.register(modEventBus);
|
||||
ModItems.register(modEventBus);
|
||||
ModMenus.register(modEventBus);
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package net.dnorberg.rivencraft.advancement;
|
||||
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.dnorberg.rivencraft.book.AgeRegistrySavedData;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
|
||||
/** Awards action-only progression after the server has committed the action. */
|
||||
public final class ProgressionAdvancements {
|
||||
public static final String LINK_PANEL = "link_panel";
|
||||
public static final String COPY_PAGE = "copy_page";
|
||||
public static final String BIND_AGE = "bind_age";
|
||||
public static final String OPEN_PORTAL = "open_portal";
|
||||
public static final String ENTER_AGE = "enter_age";
|
||||
|
||||
private ProgressionAdvancements() {}
|
||||
|
||||
public static void award(ServerPlayer player, String path) {
|
||||
var advancement = player.getServer().getAdvancements().get(
|
||||
ResourceLocation.fromNamespaceAndPath(Rivencraft.MOD_ID, "progression/" + path));
|
||||
if (advancement != null) player.getAdvancements().award(advancement, "completed");
|
||||
}
|
||||
|
||||
public static void awardAgeTravel(ServerPlayer player) {
|
||||
if (AgeRegistrySavedData.get(player.serverLevel()).findByDimension(
|
||||
player.serverLevel().dimension().location()).isPresent()) {
|
||||
award(player, ENTER_AGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package net.dnorberg.rivencraft.block;
|
||||
|
||||
import net.dnorberg.rivencraft.advancement.ProgressionAdvancements;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import net.dnorberg.rivencraft.block.entity.BookReceptacleBlockEntity;
|
||||
import net.dnorberg.rivencraft.portal.CrystalPortalNetwork;
|
||||
@@ -117,6 +118,8 @@ public final class BookReceptacleBlock extends BaseEntityBlock {
|
||||
server, pos, plan, destination));
|
||||
}
|
||||
level.playSound(null, pos, SoundEvents.ENCHANTMENT_TABLE_USE, SoundSource.BLOCKS, 1, 1);
|
||||
if (player instanceof ServerPlayer serverPlayer)
|
||||
ProgressionAdvancements.award(serverPlayer, ProgressionAdvancements.OPEN_PORTAL);
|
||||
return ItemInteractionResult.SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
package net.dnorberg.rivencraft.block;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.LevelReader;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
/** Solid footing for Age arrivals, rendered as a translucent cloud. */
|
||||
/** Solid crystal-textured footing for Age arrivals that cannot be collected. */
|
||||
public final class CloudPlatformBlock extends Block {
|
||||
public CloudPlatformBlock(Properties properties) {
|
||||
super(properties);
|
||||
@@ -18,19 +16,4 @@ public final class CloudPlatformBlock extends Block {
|
||||
public ItemStack getCloneItemStack(LevelReader level, BlockPos pos, BlockState state) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean skipRendering(BlockState state, BlockState adjacent, Direction side) {
|
||||
return adjacent.is(this) || super.skipRendering(state, adjacent, side);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean propagatesSkylightDown(BlockState state, BlockGetter level, BlockPos pos) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getLightBlock(BlockState state, BlockGetter level, BlockPos pos) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,9 @@ public final class CloudPlatformGameTests {
|
||||
helper.getLevel().setBlock(pos, state, 2);
|
||||
helper.assertTrue(!state.getCollisionShape(helper.getLevel(), pos).isEmpty(),
|
||||
"Cloud platform must support a standing player");
|
||||
helper.assertTrue(state.canOcclude(), "Arrival platform must be opaque");
|
||||
helper.assertValueEqual(state.getDestroySpeed(helper.getLevel(), pos), 0.3F,
|
||||
"Arrival platform should break as easily as glass");
|
||||
helper.assertTrue(ModBlocks.CLOUD_PLATFORM.get().getCloneItemStack(helper.getLevel(), pos, state).isEmpty(),
|
||||
"Cloud platform must not provide a pick-block item");
|
||||
helper.succeed();
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
package net.dnorberg.rivencraft.book;
|
||||
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.dnorberg.rivencraft.entity.DroppedBookEntity;
|
||||
import net.dnorberg.rivencraft.registry.ModEntities;
|
||||
import net.dnorberg.rivencraft.registry.ModItems;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.GlobalPos;
|
||||
import net.minecraft.gametest.framework.GameTest;
|
||||
import net.minecraft.gametest.framework.GameTestHelper;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
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;
|
||||
import net.neoforged.neoforge.gametest.GameTestHolder;
|
||||
import net.neoforged.neoforge.gametest.PrefixGameTestTemplate;
|
||||
|
||||
@GameTestHolder(Rivencraft.MOD_ID)
|
||||
@PrefixGameTestTemplate(false)
|
||||
public final class HandheldBookTravelGameTests {
|
||||
private HandheldBookTravelGameTests() {
|
||||
}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void successfulHandheldTravelLeavesOnlyActiveBookAtOrigin(GameTestHelper helper) {
|
||||
var player = helper.makeMockServerPlayerInLevel();
|
||||
ItemStack book = new ItemStack(ModItems.LINKING_BOOK.get());
|
||||
player.setItemInHand(InteractionHand.MAIN_HAND, book);
|
||||
player.getInventory().setItem(1, new ItemStack(Items.DIAMOND));
|
||||
var source = player.position();
|
||||
BlockPos destination = helper.absolutePos(new BlockPos(2, 1, 2));
|
||||
helper.getLevel().setBlock(destination, Blocks.AIR.defaultBlockState(), 2);
|
||||
helper.getLevel().setBlock(destination.above(), Blocks.AIR.defaultBlockState(), 2);
|
||||
LinkTarget target = new LinkTarget(GlobalPos.of(helper.getLevel().dimension(), destination), 0, 0);
|
||||
|
||||
helper.assertValueEqual(LinkingService.travelHandheld(player, InteractionHand.MAIN_HAND, book, target,
|
||||
new LinkProperties(LinkProperties.INTRA_LINKING), false), LinkingService.LinkResult.SUCCESS,
|
||||
"A valid intra-dimensional handheld link must succeed");
|
||||
helper.assertTrue(player.getMainHandItem().isEmpty(), "Only the active book must leave the hand");
|
||||
helper.assertTrue(player.getInventory().getItem(1).is(Items.DIAMOND),
|
||||
"An unrelated inventory item must remain with the player");
|
||||
var drops = helper.getLevel().getEntitiesOfClass(DroppedBookEntity.class, new AABB(source, source).inflate(2.0),
|
||||
entity -> entity.getItem().is(ModItems.LINKING_BOOK.get()));
|
||||
helper.assertValueEqual(drops.size(), 1, "Exactly one recoverable book must remain at the departure point");
|
||||
helper.assertTrue(drops.getFirst().getItem() == book,
|
||||
"The model-rendered entity must retain the exact active book stack");
|
||||
helper.assertTrue(drops.getFirst().copy() instanceof DroppedBookEntity,
|
||||
"The pickup-animation copy must keep the custom renderer's entity type");
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void rejectedHandheldTravelKeepsBook(GameTestHelper helper) {
|
||||
var player = helper.makeMockServerPlayerInLevel();
|
||||
ItemStack book = new ItemStack(ModItems.LINKING_BOOK.get());
|
||||
player.setItemInHand(InteractionHand.OFF_HAND, book);
|
||||
LinkTarget target = new LinkTarget(GlobalPos.of(helper.getLevel().dimension(), player.blockPosition()), 0, 0);
|
||||
|
||||
helper.assertValueEqual(LinkingService.travelHandheld(player, InteractionHand.OFF_HAND, book, target,
|
||||
LinkProperties.NONE, false), LinkingService.LinkResult.INTRA_LINKING_REQUIRED,
|
||||
"A same-dimension link without Intra Linking must be rejected");
|
||||
helper.assertTrue(player.getOffhandItem() == book, "The exact rejected book must be restored to its hand");
|
||||
helper.assertTrue(helper.getLevel().getEntitiesOfClass(ItemEntity.class,
|
||||
new AABB(player.position(), player.position()).inflate(2.0),
|
||||
entity -> entity.getItem().is(ModItems.LINKING_BOOK.get())).isEmpty(),
|
||||
"A rejected link must not leave a second copy at the origin");
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void disarmDropsOtherInventoryWithoutDuplicatingActiveBook(GameTestHelper helper) {
|
||||
var player = helper.makeMockServerPlayerInLevel();
|
||||
ItemStack book = new ItemStack(ModItems.LINKING_BOOK.get());
|
||||
player.setItemInHand(InteractionHand.MAIN_HAND, book);
|
||||
player.getInventory().setItem(1, new ItemStack(Items.DIAMOND));
|
||||
var source = player.position();
|
||||
BlockPos destination = helper.absolutePos(new BlockPos(2, 1, 2));
|
||||
helper.getLevel().setBlock(destination, Blocks.AIR.defaultBlockState(), 2);
|
||||
helper.getLevel().setBlock(destination.above(), Blocks.AIR.defaultBlockState(), 2);
|
||||
LinkTarget target = new LinkTarget(GlobalPos.of(helper.getLevel().dimension(), destination), 0, 0);
|
||||
|
||||
helper.assertValueEqual(LinkingService.travelHandheld(player, InteractionHand.MAIN_HAND, book, target,
|
||||
new LinkProperties(LinkProperties.INTRA_LINKING | LinkProperties.DISARM), false),
|
||||
LinkingService.LinkResult.SUCCESS, "Disarm must not block handheld travel");
|
||||
var drops = helper.getLevel().getEntitiesOfClass(ItemEntity.class, new AABB(source, source).inflate(2.0));
|
||||
helper.assertValueEqual(drops.stream().filter(entity -> entity instanceof DroppedBookEntity).count(),
|
||||
1L, "Disarm must not create a second copy of the active book");
|
||||
helper.assertValueEqual(drops.stream().filter(entity -> entity.getItem().is(Items.DIAMOND)).count(),
|
||||
1L, "Disarm must still drop unrelated inventory items");
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void originBookUsesSlowDecayWithoutVanillaDespawn(GameTestHelper helper) {
|
||||
DroppedBookEntity book = new DroppedBookEntity(helper.getLevel(),
|
||||
net.minecraft.world.phys.Vec3.ZERO, new ItemStack(ModItems.LINKING_BOOK.get()));
|
||||
CompoundTag seeded = new CompoundTag();
|
||||
book.saveWithoutId(seeded);
|
||||
seeded.putInt("BookDecayTicks", 9_999);
|
||||
book.readAdditionalSaveData(seeded);
|
||||
book.tick();
|
||||
CompoundTag saved = new CompoundTag();
|
||||
book.saveWithoutId(saved);
|
||||
helper.assertValueEqual(saved.getShort("Age"), (short)-32_768,
|
||||
"The dropped book must not expire after vanilla's five minutes");
|
||||
helper.assertValueEqual(saved.getShort("Health"), (short)4,
|
||||
"A dropped book must lose one item-health point after 10,000 loaded ticks");
|
||||
helper.assertValueEqual(saved.getInt("BookDecayTicks"), 0,
|
||||
"Slow decay must reset its interval and persist across a restart");
|
||||
DroppedBookEntity restored = new DroppedBookEntity(ModEntities.DROPPED_BOOK.get(), helper.getLevel());
|
||||
restored.readAdditionalSaveData(saved);
|
||||
CompoundTag resaved = new CompoundTag();
|
||||
restored.saveWithoutId(resaved);
|
||||
helper.assertTrue(restored.getItem().is(ModItems.LINKING_BOOK.get()),
|
||||
"The custom book entity must retain its book on reload");
|
||||
helper.assertValueEqual(resaved.getShort("Age"), (short)-32_768,
|
||||
"The custom book must remain non-despawning after reload");
|
||||
helper.assertValueEqual(resaved.getShort("Health"), (short)4,
|
||||
"Decay damage must persist after reload");
|
||||
helper.assertTrue(restored.copy().getItem().is(ModItems.LINKING_BOOK.get()),
|
||||
"A pickup-animation copy must retain the book data after reload");
|
||||
helper.succeed();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package net.dnorberg.rivencraft.book;
|
||||
|
||||
import net.dnorberg.rivencraft.registry.ModSounds;
|
||||
import net.minecraft.core.particles.ParticleTypes;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.sounds.SoundEvent;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
/** Legacy-style sound and particle bursts at both ends of a successful link. */
|
||||
public final class LinkVisualEffects {
|
||||
private static final int PARTICLE_COUNT = 50;
|
||||
|
||||
private LinkVisualEffects() {}
|
||||
|
||||
public static SoundEvent sound(LinkProperties properties, boolean portal) {
|
||||
// Legacy's Disarm flag takes precedence over a portal's sound override.
|
||||
if (properties.has(LinkProperties.DISARM)) return ModSounds.LINK_DISARM.get();
|
||||
if (portal) return ModSounds.LINK_PORTAL.get();
|
||||
if (properties.has(LinkProperties.INTRA_LINKING)) return ModSounds.LINK_INTRA.get();
|
||||
return ModSounds.LINK.get();
|
||||
}
|
||||
|
||||
public static void play(ServerLevel source, Vec3 departure, ServerPlayer player,
|
||||
LinkProperties properties, boolean portal) {
|
||||
SoundEvent event = sound(properties, portal);
|
||||
burst(source, departure, event);
|
||||
burst(player.serverLevel(), player.position(), event);
|
||||
}
|
||||
|
||||
private static void burst(ServerLevel level, Vec3 position, SoundEvent event) {
|
||||
level.playSound(null, position.x, position.y, position.z, event,
|
||||
SoundSource.PLAYERS, 0.8F, level.random.nextFloat() * 0.2F + 0.9F);
|
||||
// Legacy emitted 50 small neutral particles in a roughly one-by-two-
|
||||
// block space. Cloud particles are a vanilla gray-white stand-in for
|
||||
// its removed fixed-function particle class.
|
||||
level.sendParticles(ParticleTypes.CLOUD, position.x, position.y, position.z,
|
||||
PARTICLE_COUNT, 0.5, 1.0, 0.5, 0.005);
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,13 @@ import java.util.Optional;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
import net.dnorberg.rivencraft.compat.ImmersivePortalsChunkLoaders;
|
||||
import net.dnorberg.rivencraft.entity.DroppedBookEntity;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.tags.BlockTags;
|
||||
import net.minecraft.world.level.border.WorldBorder;
|
||||
import net.minecraft.world.level.levelgen.Heightmap;
|
||||
@@ -32,6 +35,36 @@ public final class LinkingService {
|
||||
return travel(player, target, false, properties, false);
|
||||
}
|
||||
|
||||
/** Removes only the active handheld book after a successful link, leaving it at the departure point. */
|
||||
public static LinkResult travelHandheld(ServerPlayer player, InteractionHand hand, ItemStack activeBook,
|
||||
LinkTarget target, LinkProperties properties, boolean requiresFooting) {
|
||||
if (player.getItemInHand(hand) != activeBook || activeBook.isEmpty()) {
|
||||
return LinkResult.NO_SAFE_DESTINATION;
|
||||
}
|
||||
|
||||
ServerLevel sourceLevel = player.serverLevel();
|
||||
Vec3 sourcePosition = player.position();
|
||||
player.setItemInHand(hand, ItemStack.EMPTY);
|
||||
boolean dropped = false;
|
||||
try {
|
||||
LinkResult result = requiresFooting
|
||||
? travelWithFooting(player, target, properties)
|
||||
: travel(player, target, properties);
|
||||
if (result != LinkResult.SUCCESS) return result;
|
||||
|
||||
DroppedBookEntity book = new DroppedBookEntity(sourceLevel, sourcePosition, activeBook);
|
||||
dropped = sourceLevel.addFreshEntity(book);
|
||||
return result;
|
||||
} finally {
|
||||
// Failed validation or a rejected entity spawn must never eat the book.
|
||||
if (!dropped) {
|
||||
if (player.getItemInHand(hand).isEmpty()) player.setItemInHand(hand, activeBook);
|
||||
else if (!player.getInventory().add(activeBook)) player.drop(activeBook, false);
|
||||
}
|
||||
player.containerMenu.broadcastChanges();
|
||||
}
|
||||
}
|
||||
|
||||
/** Portal travel uses the vanilla transfer if its destination is not yet loaded. */
|
||||
public static LinkResult travelPortal(ServerPlayer player, LinkTarget target, LinkProperties properties) {
|
||||
return travel(player, target, false, properties, true);
|
||||
|
||||
@@ -5,6 +5,7 @@ import net.dnorberg.rivencraft.registry.ModBlockEntities;
|
||||
import net.dnorberg.rivencraft.registry.ModBlocks;
|
||||
import net.dnorberg.rivencraft.registry.ModDataComponents;
|
||||
import net.dnorberg.rivencraft.registry.ModItems;
|
||||
import net.dnorberg.rivencraft.registry.ModEntities;
|
||||
import net.minecraft.client.renderer.item.ItemProperties;
|
||||
import net.minecraft.client.renderer.ItemBlockRenderTypes;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
@@ -34,6 +35,7 @@ public final class ClientEvents {
|
||||
|
||||
@SubscribeEvent
|
||||
public static void registerRenderers(EntityRenderersEvent.RegisterRenderers event) {
|
||||
event.registerEntityRenderer(ModEntities.DROPPED_BOOK.get(), DroppedBookRenderer::new);
|
||||
event.registerBlockEntityRenderer(ModBlockEntities.BOOK_STAND.get(), BookStandRenderer::new);
|
||||
event.registerBlockEntityRenderer(ModBlockEntities.WRITING_DESK.get(), WritingDeskRenderer::new);
|
||||
event.registerBlockEntityRenderer(ModBlockEntities.STAR_FISSURE.get(), StarFissureRenderer::new);
|
||||
@@ -78,7 +80,7 @@ public final class ClientEvents {
|
||||
ItemBlockRenderTypes.setRenderLayer(ModBlocks.MEDIUM_CRYSTAL_BUD.get(), RenderType.cutout());
|
||||
ItemBlockRenderTypes.setRenderLayer(ModBlocks.LARGE_CRYSTAL_BUD.get(), RenderType.cutout());
|
||||
ItemBlockRenderTypes.setRenderLayer(ModBlocks.CRYSTAL_CLUSTER.get(), RenderType.cutout());
|
||||
ItemBlockRenderTypes.setRenderLayer(ModBlocks.CLOUD_PLATFORM.get(), RenderType.translucent());
|
||||
ItemBlockRenderTypes.setRenderLayer(ModBlocks.INK_MIXER.get(), RenderType.cutout());
|
||||
ItemBlockRenderTypes.setRenderLayer(ModBlocks.CRYSTAL_PORTAL.get(), RenderType.translucent());
|
||||
// Immersive Portals composes the remote view before translucent blocks.
|
||||
// Draw the opaque crystal frame in that later pass so it masks the view;
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package net.dnorberg.rivencraft.client;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
import com.mojang.math.Axis;
|
||||
import net.dnorberg.rivencraft.entity.DroppedBookEntity;
|
||||
import net.dnorberg.rivencraft.registry.ModItems;
|
||||
import net.minecraft.client.model.BookModel;
|
||||
import net.minecraft.client.model.geom.ModelLayers;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.entity.EntityRenderer;
|
||||
import net.minecraft.client.renderer.entity.EntityRendererProvider;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
/** Renders the same open book model and textures as Rivencraft's book stands. */
|
||||
public final class DroppedBookRenderer extends EntityRenderer<DroppedBookEntity> {
|
||||
private static final ResourceLocation LINKING_TEXTURE = ResourceLocation.fromNamespaceAndPath(
|
||||
"rivencraft", "textures/entity/linking_book.png");
|
||||
private static final ResourceLocation DESCRIPTIVE_TEXTURE = ResourceLocation.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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(DroppedBookEntity entity, float yaw, float partialTick, PoseStack poseStack,
|
||||
MultiBufferSource buffers, int packedLight) {
|
||||
poseStack.pushPose();
|
||||
poseStack.translate(0.0F, 0.0625F, 0.0F);
|
||||
poseStack.mulPose(Axis.YP.rotationDegrees(yaw + 90.0F));
|
||||
// The opened model's covers rotate toward the YZ plane. As in Legacy's
|
||||
// dropped-book renderer, a Z-axis quarter-turn lays the pages skyward.
|
||||
poseStack.mulPose(Axis.ZP.rotationDegrees(90.0F));
|
||||
poseStack.scale(0.8F, 0.8F, 0.8F);
|
||||
model.setupAnim(0.0F, 0.0F, 0.0F, 1.2F);
|
||||
VertexConsumer vertices = buffers.getBuffer(RenderType.entitySolid(getTextureLocation(entity)));
|
||||
model.render(poseStack, vertices, packedLight, net.minecraft.client.renderer.texture.OverlayTexture.NO_OVERLAY, -1);
|
||||
poseStack.popPose();
|
||||
super.render(entity, yaw, partialTick, poseStack, buffers, packedLight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getTextureLocation(DroppedBookEntity entity) {
|
||||
return entity.getItem().is(ModItems.DESCRIPTIVE_BOOK.get()) ? DESCRIPTIVE_TEXTURE : LINKING_TEXTURE;
|
||||
}
|
||||
}
|
||||
@@ -75,7 +75,7 @@ public final class RivencraftConfig {
|
||||
"Controls automatic Return Link creation for Descriptive Book travel.",
|
||||
"ON_CREATION creates one link when an Age is first reached; ALWAYS creates one on every arrival; DISABLED creates none."
|
||||
)
|
||||
.defineEnum("return_link_mode", ReturnLinkMode.ON_CREATION);
|
||||
.defineEnum("return_link_mode", ReturnLinkMode.DISABLED);
|
||||
builder.pop();
|
||||
builder.push("libraries");
|
||||
LIBRARY_LOOT_MODE = builder.comment(
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package net.dnorberg.rivencraft.entity;
|
||||
|
||||
import net.dnorberg.rivencraft.registry.ModEntities;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
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.phys.Vec3;
|
||||
|
||||
/** A recoverable, model-rendered book left behind by handheld linking. */
|
||||
public final class DroppedBookEntity extends ItemEntity {
|
||||
private static final String DECAY_TICKS_TAG = "BookDecayTicks";
|
||||
private static final int SLOW_DECAY_INTERVAL = 10_000;
|
||||
private int decayTicks;
|
||||
|
||||
public DroppedBookEntity(EntityType<DroppedBookEntity> type, Level level) {
|
||||
super(type, level);
|
||||
}
|
||||
|
||||
public DroppedBookEntity(Level level, Vec3 position, ItemStack stack) {
|
||||
this(ModEntities.DROPPED_BOOK.get(), level);
|
||||
setPos(position.x, position.y + 0.25, position.z);
|
||||
setDeltaMovement(Vec3.ZERO);
|
||||
setItem(stack);
|
||||
setUnlimitedLifetime();
|
||||
setPickUpDelay(20);
|
||||
}
|
||||
|
||||
/** Vanilla's pickup particle calls ItemEntity.copy() and renders the returned entity. */
|
||||
@Override
|
||||
public DroppedBookEntity copy() {
|
||||
DroppedBookEntity copy = new DroppedBookEntity(ModEntities.DROPPED_BOOK.get(), level());
|
||||
copy.copyPosition(this);
|
||||
copy.setItem(getItem().copy());
|
||||
copy.setUnlimitedLifetime();
|
||||
return copy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
super.tick();
|
||||
if (level().isClientSide || isRemoved()) return;
|
||||
if (++decayTicks >= SLOW_DECAY_INTERVAL) {
|
||||
decayTicks = 0;
|
||||
hurt(damageSources().starve(), 1.0F);
|
||||
}
|
||||
if (!isRemoved() && isInWaterOrRain()) hurt(damageSources().drown(), 1.0F);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAdditionalSaveData(CompoundTag tag) {
|
||||
super.addAdditionalSaveData(tag);
|
||||
tag.putInt(DECAY_TICKS_TAG, decayTicks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readAdditionalSaveData(CompoundTag tag) {
|
||||
super.readAdditionalSaveData(tag);
|
||||
decayTicks = Math.max(0, Math.min(tag.getInt(DECAY_TICKS_TAG), SLOW_DECAY_INTERVAL - 1));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package net.dnorberg.rivencraft.gametest;
|
||||
|
||||
import java.util.List;
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.dnorberg.rivencraft.advancement.ProgressionAdvancements;
|
||||
import net.dnorberg.rivencraft.block.entity.InkMixerBlockEntity;
|
||||
import net.dnorberg.rivencraft.block.entity.WritingDeskBlockEntity;
|
||||
import net.dnorberg.rivencraft.item.PageItem;
|
||||
import net.dnorberg.rivencraft.menu.InkMixerMenu;
|
||||
import net.dnorberg.rivencraft.menu.WritingDeskMenu;
|
||||
import net.dnorberg.rivencraft.registry.ModBlocks;
|
||||
import net.dnorberg.rivencraft.registry.ModItems;
|
||||
import net.dnorberg.rivencraft.writing.SymbolReference;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.gametest.framework.GameTest;
|
||||
import net.minecraft.gametest.framework.GameTestHelper;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.inventory.ClickType;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.neoforged.neoforge.gametest.GameTestHolder;
|
||||
import net.neoforged.neoforge.gametest.PrefixGameTestTemplate;
|
||||
|
||||
@GameTestHolder(Rivencraft.MOD_ID)
|
||||
@PrefixGameTestTemplate(false)
|
||||
public final class Milestone7AdvancementGameTests {
|
||||
private Milestone7AdvancementGameTests() {}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void progressionAndRecipeUnlocksLoad(GameTestHelper helper) {
|
||||
var registry = helper.getLevel().getServer().getAdvancements();
|
||||
for (String path : List.of("root", "page", "linking_book", "link_panel",
|
||||
"copy_page", "bind_age", "enter_age", "open_portal")) {
|
||||
helper.assertTrue(registry.get(id("progression/" + path)) != null,
|
||||
"Progression advancement must load: " + path);
|
||||
}
|
||||
for (String path : List.of("book_binder", "book_receptacle", "crystal_block",
|
||||
"ink_mixer", "ink_vial", "notebook", "writing_desk")) {
|
||||
helper.assertTrue(registry.get(id("recipes/misc/" + path)) != null,
|
||||
"Recipe-book unlock must load: " + path);
|
||||
}
|
||||
var player = helper.makeMockServerPlayerInLevel();
|
||||
for (String path : List.of("book_binder", "ink_mixer", "notebook", "writing_desk")) {
|
||||
player.getAdvancements().award(registry.get(id("recipes/misc/" + path)), "has_material");
|
||||
helper.assertTrue(player.getRecipeBook().contains(id(path)),
|
||||
"Discovering the material must unlock the " + path + " recipe");
|
||||
}
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void actionAwardsAreServerOwnedAndPerPlayer(GameTestHelper helper) {
|
||||
var first = helper.makeMockServerPlayerInLevel();
|
||||
var second = helper.makeMockServerPlayerInLevel();
|
||||
var advancement = helper.getLevel().getServer().getAdvancements().get(id("progression/copy_page"));
|
||||
helper.assertFalse(first.getAdvancements().getOrStartProgress(advancement).isDone(),
|
||||
"Opening a menu or preview must not award a copied Page");
|
||||
helper.assertFalse(second.getAdvancements().getOrStartProgress(advancement).isDone(),
|
||||
"Another player must not inherit the advancement");
|
||||
ProgressionAdvancements.awardAgeTravel(first);
|
||||
var travel = helper.getLevel().getServer().getAdvancements().get(id("progression/enter_age"));
|
||||
helper.assertFalse(first.getAdvancements().getOrStartProgress(travel).isDone(),
|
||||
"Overworld travel or a preview must not count as entering an Age");
|
||||
ProgressionAdvancements.award(first, ProgressionAdvancements.COPY_PAGE);
|
||||
helper.assertTrue(first.getAdvancements().getOrStartProgress(advancement).isDone(),
|
||||
"The server must award the player who completed the action");
|
||||
helper.assertFalse(second.getAdvancements().getOrStartProgress(advancement).isDone(),
|
||||
"Advancement progress must remain per-player in multiplayer");
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void mixerPreviewDoesNotAwardButTakingPanelDoes(GameTestHelper helper) {
|
||||
BlockPos pos = helper.absolutePos(new BlockPos(1, 1, 1));
|
||||
helper.getLevel().setBlock(pos, ModBlocks.INK_MIXER.get().defaultBlockState(), 3);
|
||||
var mixer = (InkMixerBlockEntity) helper.getLevel().getBlockEntity(pos);
|
||||
mixer.setItem(InkMixerBlockEntity.PAPER, new ItemStack(Items.PAPER));
|
||||
mixer.setItem(InkMixerBlockEntity.INK, new ItemStack(ModItems.INK_VIAL.get()));
|
||||
var player = nearbyPlayer(helper, pos);
|
||||
var menu = new InkMixerMenu(1, player.getInventory(), mixer);
|
||||
helper.assertFalse(done(player, ProgressionAdvancements.LINK_PANEL),
|
||||
"Opening the Ink Mixer must not award its milestone");
|
||||
menu.clicked(InkMixerMenu.RESULT, 0, ClickType.PICKUP, player);
|
||||
helper.assertTrue(done(player, ProgressionAdvancements.LINK_PANEL),
|
||||
"Taking the completed Link Panel must award its milestone");
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void deskPreviewDoesNotAwardButCopyingDoes(GameTestHelper helper) {
|
||||
BlockPos pos = helper.absolutePos(new BlockPos(1, 1, 1));
|
||||
helper.getLevel().setBlock(pos, ModBlocks.WRITING_DESK.get().defaultBlockState(), 3);
|
||||
var desk = (WritingDeskBlockEntity) helper.getLevel().getBlockEntity(pos);
|
||||
desk.setItem(0, PageItem.createSymbolPage(new SymbolReference(id("terrain/standard"))));
|
||||
desk.setItem(WritingDeskBlockEntity.PAPER, new ItemStack(Items.PAPER));
|
||||
desk.setItem(WritingDeskBlockEntity.INK, new ItemStack(ModItems.INK_VIAL.get()));
|
||||
var player = nearbyPlayer(helper, pos);
|
||||
var menu = new WritingDeskMenu(1, player.getInventory(), desk);
|
||||
helper.assertFalse(done(player, ProgressionAdvancements.COPY_PAGE),
|
||||
"Opening the Writing Desk must not award a copied Page");
|
||||
menu.clicked(WritingDeskMenu.COPY_RESULT, 0, ClickType.PICKUP, player);
|
||||
helper.assertTrue(done(player, ProgressionAdvancements.COPY_PAGE),
|
||||
"Taking a copied Page must award its milestone");
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
private static ServerPlayer nearbyPlayer(GameTestHelper helper, BlockPos pos) {
|
||||
var player = helper.makeMockServerPlayerInLevel();
|
||||
player.moveTo(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5);
|
||||
return player;
|
||||
}
|
||||
|
||||
private static boolean done(ServerPlayer player, String path) {
|
||||
var advancement = player.getServer().getAdvancements().get(id("progression/" + path));
|
||||
return player.getAdvancements().getOrStartProgress(advancement).isDone();
|
||||
}
|
||||
|
||||
private static ResourceLocation id(String path) {
|
||||
return ResourceLocation.fromNamespaceAndPath(Rivencraft.MOD_ID, path);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package net.dnorberg.rivencraft.gametest;
|
||||
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.dnorberg.rivencraft.book.LinkProperties;
|
||||
import net.dnorberg.rivencraft.book.LinkVisualEffects;
|
||||
import net.dnorberg.rivencraft.registry.ModSounds;
|
||||
import net.minecraft.gametest.framework.GameTest;
|
||||
import net.minecraft.gametest.framework.GameTestHelper;
|
||||
import net.neoforged.neoforge.gametest.GameTestHolder;
|
||||
import net.neoforged.neoforge.gametest.PrefixGameTestTemplate;
|
||||
|
||||
@GameTestHolder(Rivencraft.MOD_ID)
|
||||
@PrefixGameTestTemplate(false)
|
||||
public final class Milestone7PolishGameTests {
|
||||
private Milestone7PolishGameTests() {}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void linkSoundPriorityMatchesLegacy(GameTestHelper helper) {
|
||||
helper.assertTrue(LinkVisualEffects.sound(LinkProperties.NONE, false) == ModSounds.LINK.get(),
|
||||
"Ordinary book travel uses the Legacy link sound");
|
||||
helper.assertTrue(LinkVisualEffects.sound(new LinkProperties(LinkProperties.INTRA_LINKING), false)
|
||||
== ModSounds.LINK_INTRA.get(),
|
||||
"Intra Linking uses its Legacy sound");
|
||||
helper.assertTrue(LinkVisualEffects.sound(LinkProperties.NONE, true) == ModSounds.LINK_PORTAL.get(),
|
||||
"Portal travel uses its Legacy sound");
|
||||
helper.assertTrue(LinkVisualEffects.sound(new LinkProperties(LinkProperties.DISARM), true)
|
||||
== ModSounds.LINK_DISARM.get(),
|
||||
"Legacy Disarm takes precedence over the portal sound");
|
||||
helper.succeed();
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,28 @@
|
||||
package net.dnorberg.rivencraft.item;
|
||||
|
||||
import net.dnorberg.rivencraft.advancement.ProgressionAdvancements;
|
||||
import java.util.List;
|
||||
import net.dnorberg.rivencraft.book.AgeReference;
|
||||
import net.dnorberg.rivencraft.book.AgeVisitSavedData;
|
||||
import net.dnorberg.rivencraft.book.LinkTarget;
|
||||
import net.dnorberg.rivencraft.book.LinkProperties;
|
||||
import net.dnorberg.rivencraft.book.LinkingService;
|
||||
import net.dnorberg.rivencraft.book.LinkVisualEffects;
|
||||
import net.dnorberg.rivencraft.book.AgeDimensionService;
|
||||
import net.dnorberg.rivencraft.config.RivencraftConfig;
|
||||
import net.dnorberg.rivencraft.registry.ModDataComponents;
|
||||
import net.dnorberg.rivencraft.registry.ModItems;
|
||||
import net.dnorberg.rivencraft.registry.ModSounds;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.MutableComponent;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.TooltipFlag;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
/**
|
||||
* A book that will eventually describe and link to a generated Age.
|
||||
@@ -40,14 +42,18 @@ public final class DescriptiveBookItem extends Item {
|
||||
}
|
||||
|
||||
public static boolean useWrittenBook(ServerPlayer player, ItemStack stack) {
|
||||
return useWrittenBook(player, stack, false);
|
||||
return useWrittenBook(player, stack, false, null);
|
||||
}
|
||||
|
||||
public static boolean useHandheldBook(ServerPlayer player, InteractionHand hand) {
|
||||
return useWrittenBook(player, player.getItemInHand(hand), false, hand);
|
||||
}
|
||||
|
||||
public static boolean usePortalBook(ServerPlayer player, ItemStack stack) {
|
||||
return useWrittenBook(player, stack, true);
|
||||
return useWrittenBook(player, stack, true, null);
|
||||
}
|
||||
|
||||
private static boolean useWrittenBook(ServerPlayer player, ItemStack stack, boolean portal) {
|
||||
private static boolean useWrittenBook(ServerPlayer player, ItemStack stack, boolean portal, InteractionHand hand) {
|
||||
AgeReference age = stack.get(ModDataComponents.AGE_REFERENCE.get());
|
||||
if (age == null) {
|
||||
player.displayClientMessage(Component.translatable("message.rivencraft.descriptive_book.unwritten"), true);
|
||||
@@ -62,12 +68,13 @@ public final class DescriptiveBookItem extends Item {
|
||||
}
|
||||
|
||||
ServerLevel sourceLevel = player.serverLevel();
|
||||
var sourcePosition = player.blockPosition();
|
||||
Vec3 sourcePosition = player.position();
|
||||
LinkProperties properties = stack.getOrDefault(ModDataComponents.LINK_PROPERTIES.get(), LinkProperties.NONE)
|
||||
.forDescriptiveBook();
|
||||
LinkingService.LinkResult result = portal
|
||||
? LinkingService.travelDescriptivePortal(player, ageTarget, properties)
|
||||
: LinkingService.travelWithFooting(player, ageTarget, properties);
|
||||
: hand == null ? LinkingService.travelWithFooting(player, ageTarget, properties)
|
||||
: LinkingService.travelHandheld(player, hand, stack, ageTarget, properties, true);
|
||||
if (result != LinkingService.LinkResult.SUCCESS) {
|
||||
player.displayClientMessage(Component.translatable("message.rivencraft.descriptive_book.unsafe"), true);
|
||||
return false;
|
||||
@@ -85,10 +92,8 @@ public final class DescriptiveBookItem extends Item {
|
||||
player.drop(returnBook, false);
|
||||
}
|
||||
}
|
||||
sourceLevel.playSound(null, sourcePosition, (portal ? ModSounds.LINK_PORTAL : ModSounds.LINK).get(), SoundSource.PLAYERS,
|
||||
0.8F, sourceLevel.random.nextFloat() * 0.2F + 0.9F);
|
||||
player.level().playSound(null, player.blockPosition(), (portal ? ModSounds.LINK_PORTAL : ModSounds.LINK).get(), SoundSource.PLAYERS,
|
||||
0.8F, player.level().random.nextFloat() * 0.2F + 0.9F);
|
||||
LinkVisualEffects.play(sourceLevel, sourcePosition, player, properties, portal);
|
||||
ProgressionAdvancements.awardAgeTravel(player);
|
||||
player.displayClientMessage(Component.translatable(
|
||||
createReturnLink ? "message.rivencraft.descriptive_book.linked_first" : "message.rivencraft.descriptive_book.linked",
|
||||
age.name()
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
package net.dnorberg.rivencraft.item;
|
||||
|
||||
import net.dnorberg.rivencraft.advancement.ProgressionAdvancements;
|
||||
import java.util.List;
|
||||
import net.dnorberg.rivencraft.book.LinkTarget;
|
||||
import net.dnorberg.rivencraft.book.AgeRegistrySavedData;
|
||||
import net.dnorberg.rivencraft.book.LinkingService;
|
||||
import net.dnorberg.rivencraft.book.LinkVisualEffects;
|
||||
import net.dnorberg.rivencraft.book.LinkProperties;
|
||||
import net.dnorberg.rivencraft.registry.ModDataComponents;
|
||||
import net.dnorberg.rivencraft.registry.ModSounds;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResultHolder;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
@@ -21,6 +21,7 @@ import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.TooltipFlag;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
public final class LinkingBookItem extends Item {
|
||||
public LinkingBookItem(Properties properties) {
|
||||
@@ -42,37 +43,35 @@ public final class LinkingBookItem extends Item {
|
||||
}
|
||||
|
||||
public static InteractionResultHolder<ItemStack> useBoundBook(ServerPlayer player, ItemStack stack) {
|
||||
return useBoundBook(player, stack, false);
|
||||
return useBoundBook(player, stack, false, null);
|
||||
}
|
||||
|
||||
public static InteractionResultHolder<ItemStack> useHandheldBook(ServerPlayer player, InteractionHand hand) {
|
||||
return useBoundBook(player, player.getItemInHand(hand), false, hand);
|
||||
}
|
||||
|
||||
public static InteractionResultHolder<ItemStack> usePortalBook(ServerPlayer player, ItemStack stack) {
|
||||
return useBoundBook(player, stack, true);
|
||||
return useBoundBook(player, stack, true, null);
|
||||
}
|
||||
|
||||
private static InteractionResultHolder<ItemStack> useBoundBook(ServerPlayer player, ItemStack stack, boolean portal) {
|
||||
private static InteractionResultHolder<ItemStack> useBoundBook(ServerPlayer player, ItemStack stack, boolean portal,
|
||||
InteractionHand hand) {
|
||||
LinkTarget target = stack.get(ModDataComponents.LINK_TARGET.get());
|
||||
if (target == null) {
|
||||
player.displayClientMessage(Component.translatable("message.rivencraft.linking_book.unbound"), true);
|
||||
return InteractionResultHolder.fail(stack);
|
||||
}
|
||||
|
||||
Level sourceLevel = player.level();
|
||||
BlockPos sourcePosition = player.blockPosition();
|
||||
ServerLevel sourceLevel = player.serverLevel();
|
||||
Vec3 sourcePosition = player.position();
|
||||
LinkProperties properties = stack.getOrDefault(ModDataComponents.LINK_PROPERTIES.get(), LinkProperties.NONE);
|
||||
LinkingService.LinkResult result = portal
|
||||
? LinkingService.travelPortal(player, target, properties)
|
||||
: LinkingService.travel(player, target, properties);
|
||||
: hand == null ? LinkingService.travel(player, target, properties)
|
||||
: LinkingService.travelHandheld(player, hand, stack, target, properties, false);
|
||||
if (result == LinkingService.LinkResult.SUCCESS) {
|
||||
sourceLevel.playSound(null, sourcePosition, (portal ? ModSounds.LINK_PORTAL : ModSounds.LINK).get(), SoundSource.PLAYERS,
|
||||
0.8F, sourceLevel.random.nextFloat() * 0.2F + 0.9F);
|
||||
player.level().playSound(
|
||||
null,
|
||||
player.blockPosition(),
|
||||
(portal ? ModSounds.LINK_PORTAL : ModSounds.LINK).get(),
|
||||
SoundSource.PLAYERS,
|
||||
0.8F,
|
||||
player.level().random.nextFloat() * 0.2F + 0.9F
|
||||
);
|
||||
LinkVisualEffects.play(sourceLevel, sourcePosition, player, properties, portal);
|
||||
ProgressionAdvancements.awardAgeTravel(player);
|
||||
player.displayClientMessage(Component.translatable("message.rivencraft.linking_book.linked"), true);
|
||||
return InteractionResultHolder.success(stack);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package net.dnorberg.rivencraft.menu;
|
||||
|
||||
import net.dnorberg.rivencraft.advancement.ProgressionAdvancements;
|
||||
import java.util.Optional;
|
||||
import net.dnorberg.rivencraft.block.entity.BookBinderBlockEntity;
|
||||
import net.dnorberg.rivencraft.item.PageItem;
|
||||
@@ -9,6 +10,7 @@ import net.dnorberg.rivencraft.registry.ModMenus;
|
||||
import net.dnorberg.rivencraft.writing.PageContent;
|
||||
import net.dnorberg.rivencraft.writing.WritingGrammar;
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.SimpleContainer;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
@@ -210,7 +212,9 @@ public final class BookBinderMenu extends AbstractContainerMenu {
|
||||
if (!moveItemStackTo(remainder, PLAYER_START, slots.size(), true) || !remainder.isEmpty()) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
binder.consumeAssembly();
|
||||
if (!binder.consumeAssembly()) return ItemStack.EMPTY;
|
||||
if (player instanceof ServerPlayer serverPlayer)
|
||||
ProgressionAdvancements.award(serverPlayer, ProgressionAdvancements.BIND_AGE);
|
||||
refreshResult();
|
||||
return book;
|
||||
}
|
||||
@@ -284,7 +288,11 @@ public final class BookBinderMenu extends AbstractContainerMenu {
|
||||
return clientSide ? hasItem() : stillValid(player) && binder.canAssemble();
|
||||
}
|
||||
@Override public void onTake(Player player, ItemStack stack) {
|
||||
if (!clientSide && binder.consumeAssembly()) refreshResult();
|
||||
if (!clientSide && binder.consumeAssembly()) {
|
||||
if (player instanceof ServerPlayer serverPlayer)
|
||||
ProgressionAdvancements.award(serverPlayer, ProgressionAdvancements.BIND_AGE);
|
||||
refreshResult();
|
||||
}
|
||||
super.onTake(player, stack);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package net.dnorberg.rivencraft.menu;
|
||||
|
||||
import net.dnorberg.rivencraft.advancement.ProgressionAdvancements;
|
||||
import net.dnorberg.rivencraft.block.entity.InkMixerBlockEntity;
|
||||
import net.dnorberg.rivencraft.item.PageItem;
|
||||
import net.dnorberg.rivencraft.registry.ModItems;
|
||||
import net.dnorberg.rivencraft.registry.ModMenus;
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.SimpleContainer;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
@@ -109,6 +111,8 @@ public final class InkMixerMenu extends AbstractContainerMenu {
|
||||
if (!moveItemStackTo(remainder, PLAYER_START, slots.size(), true) || !remainder.isEmpty())
|
||||
return ItemStack.EMPTY;
|
||||
if (!mixer.consumeLinkPanelMaterials()) return ItemStack.EMPTY;
|
||||
if (player instanceof ServerPlayer serverPlayer)
|
||||
ProgressionAdvancements.award(serverPlayer, ProgressionAdvancements.LINK_PANEL);
|
||||
refreshResult();
|
||||
return output;
|
||||
}
|
||||
@@ -148,7 +152,11 @@ public final class InkMixerMenu extends AbstractContainerMenu {
|
||||
return clientSide ? hasItem() : stillValid(player) && mixer.canCreateLinkPanel();
|
||||
}
|
||||
@Override public void onTake(Player player, ItemStack stack) {
|
||||
if (!clientSide && mixer.consumeLinkPanelMaterials()) refreshResult();
|
||||
if (!clientSide && mixer.consumeLinkPanelMaterials()) {
|
||||
if (player instanceof ServerPlayer serverPlayer)
|
||||
ProgressionAdvancements.award(serverPlayer, ProgressionAdvancements.LINK_PANEL);
|
||||
refreshResult();
|
||||
}
|
||||
super.onTake(player, stack);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package net.dnorberg.rivencraft.menu;
|
||||
|
||||
import net.dnorberg.rivencraft.advancement.ProgressionAdvancements;
|
||||
import net.dnorberg.rivencraft.block.entity.WritingDeskBlockEntity;
|
||||
import net.dnorberg.rivencraft.item.PageItem;
|
||||
import net.dnorberg.rivencraft.registry.ModDataComponents;
|
||||
@@ -317,7 +318,9 @@ public final class WritingDeskMenu extends AbstractContainerMenu {
|
||||
if (!moveItemStackTo(movingCopy, PLAYER_START, slots.size(), true) || !movingCopy.isEmpty()) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
blockEntity.consumeCopyMaterials();
|
||||
if (!blockEntity.consumeCopyMaterials()) return ItemStack.EMPTY;
|
||||
if (player instanceof ServerPlayer serverPlayer)
|
||||
ProgressionAdvancements.award(serverPlayer, ProgressionAdvancements.COPY_PAGE);
|
||||
refreshOutput();
|
||||
return copy;
|
||||
}
|
||||
@@ -434,7 +437,11 @@ public final class WritingDeskMenu extends AbstractContainerMenu {
|
||||
return clientSide ? hasItem() : WritingDeskMenu.this.stillValid(player) && canCopy();
|
||||
}
|
||||
@Override public void onTake(Player player, ItemStack stack) {
|
||||
if (!clientSide && blockEntity.consumeCopyMaterials()) refreshOutput();
|
||||
if (!clientSide && blockEntity.consumeCopyMaterials()) {
|
||||
if (player instanceof ServerPlayer serverPlayer)
|
||||
ProgressionAdvancements.award(serverPlayer, ProgressionAdvancements.COPY_PAGE);
|
||||
refreshOutput();
|
||||
}
|
||||
super.onTake(player, stack);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ public final class ModNetworking {
|
||||
|
||||
ItemStack stack = player.getItemInHand(hand(payload.offhand()));
|
||||
if (stack.is(ModItems.LINKING_BOOK.get()) && stack.has(ModDataComponents.LINK_TARGET.get())) {
|
||||
LinkingBookItem.useBoundBook(player, stack);
|
||||
LinkingBookItem.useHandheldBook(player, hand(payload.offhand()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,7 +245,8 @@ public final class ModNetworking {
|
||||
}
|
||||
|
||||
if (stack.is(ModItems.DESCRIPTIVE_BOOK.get())) {
|
||||
DescriptiveBookItem.useWrittenBook(player, stack);
|
||||
if (payload.fromStand()) DescriptiveBookItem.useWrittenBook(player, stack);
|
||||
else DescriptiveBookItem.useHandheldBook(player, hand(payload.offhand()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,8 +70,8 @@ public final class ModBlocks {
|
||||
DecayBlock::new, BlockBehaviour.Properties.ofFullCopy(Blocks.STONE)
|
||||
.strength(5.0F, 10.0F).noLootTable());
|
||||
public static final DeferredBlock<CloudPlatformBlock> CLOUD_PLATFORM = BLOCKS.registerBlock("cloud_platform",
|
||||
CloudPlatformBlock::new, BlockBehaviour.Properties.ofFullCopy(Blocks.WHITE_STAINED_GLASS)
|
||||
.strength(1.5F).noOcclusion().noLootTable());
|
||||
CloudPlatformBlock::new, BlockBehaviour.Properties.ofFullCopy(Blocks.AMETHYST_BLOCK)
|
||||
.mapColor(MapColor.COLOR_BLUE).strength(0.3F).noLootTable());
|
||||
|
||||
private ModBlocks() {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package net.dnorberg.rivencraft.registry;
|
||||
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.dnorberg.rivencraft.entity.DroppedBookEntity;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.MobCategory;
|
||||
import net.neoforged.bus.api.IEventBus;
|
||||
import net.neoforged.neoforge.registries.DeferredHolder;
|
||||
import net.neoforged.neoforge.registries.DeferredRegister;
|
||||
|
||||
public final class ModEntities {
|
||||
private static final DeferredRegister<EntityType<?>> ENTITIES =
|
||||
DeferredRegister.create(Registries.ENTITY_TYPE, Rivencraft.MOD_ID);
|
||||
|
||||
public static final DeferredHolder<EntityType<?>, EntityType<DroppedBookEntity>> DROPPED_BOOK =
|
||||
ENTITIES.register("dropped_book", () -> EntityType.Builder
|
||||
.<DroppedBookEntity>of(DroppedBookEntity::new, MobCategory.MISC)
|
||||
.sized(0.65F, 0.4F)
|
||||
.clientTrackingRange(10)
|
||||
.updateInterval(20)
|
||||
.build(Rivencraft.MOD_ID + ":dropped_book"));
|
||||
|
||||
private ModEntities() {
|
||||
}
|
||||
|
||||
public static void register(IEventBus bus) {
|
||||
ENTITIES.register(bus);
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,8 @@ public final class ModSounds {
|
||||
DeferredRegister.create(Registries.SOUND_EVENT, Rivencraft.MOD_ID);
|
||||
|
||||
public static final DeferredHolder<SoundEvent, SoundEvent> LINK = register("linking.link");
|
||||
public static final DeferredHolder<SoundEvent, SoundEvent> LINK_DISARM = register("linking.link-disarm");
|
||||
public static final DeferredHolder<SoundEvent, SoundEvent> LINK_INTRA = register("linking.link-intra");
|
||||
public static final DeferredHolder<SoundEvent, SoundEvent> LINK_FISSURE = register("linking.link-fissure");
|
||||
public static final DeferredHolder<SoundEvent, SoundEvent> LINK_PORTAL = register("linking.link-portal");
|
||||
public static final DeferredHolder<SoundEvent, SoundEvent> METEOR_ROAR = register("entity.meteor.roar");
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"container.rivencraft.book_binder": "Book Binder",
|
||||
"block.rivencraft.ink_mixer": "Ink Mixer",
|
||||
"block.rivencraft.decay": "Decay",
|
||||
"block.rivencraft.cloud_platform": "Cloud Platform",
|
||||
"block.rivencraft.cloud_platform": "Crystal Platform",
|
||||
"container.rivencraft.ink_mixer": "Ink Mixer",
|
||||
"screen.rivencraft.ink_mixer.ink": "Ink: %s/%s",
|
||||
"screen.rivencraft.ink_mixer.additive_hint": "Click the basin with an additive; right-click adds one.",
|
||||
@@ -369,5 +369,21 @@
|
||||
"symbol.rivencraft.corrupted.medium": "Corrupted Page",
|
||||
"symbol.rivencraft.corrupted.medium.description": "The writing shifts beyond comprehension.",
|
||||
"symbol.rivencraft.corrupted.small": "Corrupted Page",
|
||||
"symbol.rivencraft.corrupted.small.description": "The writing shifts beyond comprehension."
|
||||
"symbol.rivencraft.corrupted.small.description": "The writing shifts beyond comprehension.",
|
||||
"advancement.rivencraft.root.title": "The Ending Has Not Yet Been Written",
|
||||
"advancement.rivencraft.root.description": "Begin the Art of writing and linking Ages.",
|
||||
"advancement.rivencraft.page.title": "A Strange Page",
|
||||
"advancement.rivencraft.page.description": "Find a Page marked with the language of Ages.",
|
||||
"advancement.rivencraft.linking_book.title": "A Way Back",
|
||||
"advancement.rivencraft.linking_book.description": "Obtain a Linking Book and remember where you came from.",
|
||||
"advancement.rivencraft.link_panel.title": "D'ni Ink",
|
||||
"advancement.rivencraft.link_panel.description": "Create a Link Panel at the Ink Mixer.",
|
||||
"advancement.rivencraft.copy_page.title": "The Art of Writing",
|
||||
"advancement.rivencraft.copy_page.description": "Copy a Page at the Writing Desk.",
|
||||
"advancement.rivencraft.bind_age.title": "A Book of Ages",
|
||||
"advancement.rivencraft.bind_age.description": "Bind a completed Descriptive Book.",
|
||||
"advancement.rivencraft.enter_age.title": "The Journey Begins",
|
||||
"advancement.rivencraft.enter_age.description": "Safely link into an Age you have written.",
|
||||
"advancement.rivencraft.open_portal.title": "A Window Between Ages",
|
||||
"advancement.rivencraft.open_portal.description": "Activate a Crystal Portal with a bound book."
|
||||
}
|
||||
@@ -39,6 +39,28 @@
|
||||
"down": {"texture":"#press","uv":[8.5,0,10,7]}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [6, 10, 1], "to": [7, 14, 15],
|
||||
"faces": {
|
||||
"north": {"texture":"#press","uv":[7,13,7.5,15]},
|
||||
"east": {"texture":"#press","uv":[7.5,13,14.5,15]},
|
||||
"south": {"texture":"#press","uv":[14.5,13,15,15]},
|
||||
"west": {"texture":"#press","uv":[0,13,7,15]},
|
||||
"up": {"texture":"#press","uv":[0,6,0.5,13]},
|
||||
"down": {"texture":"#press","uv":[0.5,6,1,13]}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [9, 10, 1], "to": [10, 14, 15],
|
||||
"faces": {
|
||||
"north": {"texture":"#press","uv":[7,13,7.5,15]},
|
||||
"east": {"texture":"#press","uv":[0,13,7,15]},
|
||||
"south": {"texture":"#press","uv":[14.5,13,15,15]},
|
||||
"west": {"texture":"#press","uv":[7.5,13,14.5,15]},
|
||||
"up": {"texture":"#press","uv":[0,6,0.5,13]},
|
||||
"down": {"texture":"#press","uv":[0.5,6,1,13]}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [3, 11, 3], "to": [13, 12, 4],
|
||||
"faces": {
|
||||
@@ -56,6 +78,17 @@
|
||||
"up": {"texture":"#press","uv":[0.5,15,5.5,15.5]},
|
||||
"down": {"texture":"#press","uv":[5.5,15,10.5,15.5]}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [7, 10, 4.5], "to": [9, 15, 11.5],
|
||||
"faces": {
|
||||
"north": {"texture":"#press","uv":[4.5,10.5,5.5,13]},
|
||||
"east": {"texture":"#press","uv":[1,10.5,4.5,13]},
|
||||
"south": {"texture":"#press","uv":[9,10,10,13]},
|
||||
"west": {"texture":"#press","uv":[5.5,10.5,9,13]},
|
||||
"up": {"texture":"#press","uv":[4.5,7,5.5,10.5]},
|
||||
"down": {"texture":"#press","uv":[5.5,7,6.5,10.5]}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
{"parent":"minecraft:block/cube_all","render_type":"minecraft:translucent","textures":{"all":"rivencraft:block/cloud_platform"}}
|
||||
{"parent":"minecraft:block/cube_all","textures":{"all":"rivencraft:block/crystal_block"}}
|
||||
@@ -1 +1 @@
|
||||
{"textures":{"portal":"rivencraft:block/crystal_portal"},"elements":[{"from":[0,0,7.5],"to":[16,16,8.5],"shade":false,"faces":{"north":{"texture":"#portal"},"south":{"texture":"#portal"}}}]}
|
||||
{"render_type":"minecraft:translucent","ambientocclusion":false,"textures":{"particle":"rivencraft:block/crystal_portal","portal":"rivencraft:block/crystal_portal"},"elements":[{"from":[0,0,6],"to":[16,16,10],"shade":false,"faces":{"north":{"texture":"#portal"},"south":{"texture":"#portal"}}}]}
|
||||
@@ -1,23 +1,971 @@
|
||||
{
|
||||
"parent": "minecraft:block/block",
|
||||
"textures": {
|
||||
"particle": "rivencraft:block/ink_mixer_side",
|
||||
"side": "rivencraft:block/ink_mixer_side",
|
||||
"top": "rivencraft:block/ink_mixer_top",
|
||||
"bottom": "rivencraft:block/ink_mixer_bottom"
|
||||
"0": "rivencraft:block/inkwell_base",
|
||||
"1": "rivencraft:block/inkwell_cup",
|
||||
"2": "rivencraft:block/ink",
|
||||
"3": "rivencraft:block/ink_glass",
|
||||
"particle": "rivencraft:block/inkwell_base",
|
||||
"paper": "minecraft:block/white_concrete"
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"from": [0, 0, 0],
|
||||
"to": [16, 12, 16],
|
||||
"from": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"to": [
|
||||
16,
|
||||
9,
|
||||
16
|
||||
],
|
||||
"faces": {
|
||||
"north": { "texture": "#side", "uv": [0, 4, 16, 16] },
|
||||
"east": { "texture": "#side", "uv": [0, 4, 16, 16] },
|
||||
"south": { "texture": "#side", "uv": [0, 4, 16, 16] },
|
||||
"west": { "texture": "#side", "uv": [0, 4, 16, 16] },
|
||||
"up": { "texture": "#top" },
|
||||
"down": { "texture": "#bottom" }
|
||||
"north": {
|
||||
"texture": "#0",
|
||||
"uv": [
|
||||
0,
|
||||
5,
|
||||
4,
|
||||
7
|
||||
]
|
||||
},
|
||||
"east": {
|
||||
"texture": "#0",
|
||||
"uv": [
|
||||
4,
|
||||
5,
|
||||
8,
|
||||
7
|
||||
]
|
||||
},
|
||||
"south": {
|
||||
"texture": "#0",
|
||||
"uv": [
|
||||
8,
|
||||
5,
|
||||
12,
|
||||
7
|
||||
]
|
||||
},
|
||||
"west": {
|
||||
"texture": "#0",
|
||||
"uv": [
|
||||
12,
|
||||
5,
|
||||
16,
|
||||
7
|
||||
]
|
||||
},
|
||||
"down": {
|
||||
"texture": "#0",
|
||||
"uv": [
|
||||
8,
|
||||
0,
|
||||
12,
|
||||
4
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
0,
|
||||
9,
|
||||
0
|
||||
],
|
||||
"to": [
|
||||
16,
|
||||
12,
|
||||
16
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"texture": "#0",
|
||||
"uv": [
|
||||
0,
|
||||
4,
|
||||
4,
|
||||
5
|
||||
]
|
||||
},
|
||||
"east": {
|
||||
"texture": "#0",
|
||||
"uv": [
|
||||
4,
|
||||
4,
|
||||
8,
|
||||
5
|
||||
]
|
||||
},
|
||||
"south": {
|
||||
"texture": "#0",
|
||||
"uv": [
|
||||
8,
|
||||
4,
|
||||
12,
|
||||
5
|
||||
]
|
||||
},
|
||||
"west": {
|
||||
"texture": "#0",
|
||||
"uv": [
|
||||
12,
|
||||
4,
|
||||
16,
|
||||
5
|
||||
]
|
||||
},
|
||||
"up": {
|
||||
"texture": "#0",
|
||||
"uv": [
|
||||
4,
|
||||
0,
|
||||
8,
|
||||
4
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
9,
|
||||
12,
|
||||
6
|
||||
],
|
||||
"to": [
|
||||
13,
|
||||
13,
|
||||
10
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"texture": "#1",
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
4,
|
||||
1
|
||||
]
|
||||
},
|
||||
"east": {
|
||||
"texture": "#1",
|
||||
"uv": [
|
||||
4,
|
||||
0,
|
||||
8,
|
||||
1
|
||||
]
|
||||
},
|
||||
"south": {
|
||||
"texture": "#1",
|
||||
"uv": [
|
||||
8,
|
||||
0,
|
||||
12,
|
||||
1
|
||||
]
|
||||
},
|
||||
"west": {
|
||||
"texture": "#1",
|
||||
"uv": [
|
||||
12,
|
||||
0,
|
||||
16,
|
||||
1
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
8,
|
||||
13,
|
||||
5
|
||||
],
|
||||
"to": [
|
||||
14,
|
||||
16,
|
||||
6
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"texture": "#1",
|
||||
"uv": [
|
||||
7,
|
||||
1,
|
||||
13,
|
||||
4
|
||||
]
|
||||
},
|
||||
"east": {
|
||||
"texture": "#1",
|
||||
"uv": [
|
||||
6,
|
||||
1,
|
||||
7,
|
||||
4
|
||||
]
|
||||
},
|
||||
"south": {
|
||||
"texture": "#1",
|
||||
"uv": [
|
||||
0,
|
||||
1,
|
||||
6,
|
||||
4
|
||||
]
|
||||
},
|
||||
"west": {
|
||||
"texture": "#1",
|
||||
"uv": [
|
||||
13,
|
||||
1,
|
||||
14,
|
||||
4
|
||||
]
|
||||
},
|
||||
"up": {
|
||||
"texture": "#1",
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
6,
|
||||
1
|
||||
]
|
||||
},
|
||||
"down": {
|
||||
"texture": "#1",
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
6,
|
||||
1
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
8,
|
||||
13,
|
||||
10
|
||||
],
|
||||
"to": [
|
||||
14,
|
||||
16,
|
||||
11
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"texture": "#1",
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
6,
|
||||
3
|
||||
]
|
||||
},
|
||||
"east": {
|
||||
"texture": "#1",
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
3
|
||||
]
|
||||
},
|
||||
"south": {
|
||||
"texture": "#1",
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
6,
|
||||
3
|
||||
]
|
||||
},
|
||||
"west": {
|
||||
"texture": "#1",
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
3
|
||||
]
|
||||
},
|
||||
"up": {
|
||||
"texture": "#1",
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
6,
|
||||
1
|
||||
]
|
||||
},
|
||||
"down": {
|
||||
"texture": "#1",
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
6,
|
||||
1
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
8,
|
||||
13,
|
||||
6
|
||||
],
|
||||
"to": [
|
||||
9,
|
||||
16,
|
||||
10
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"texture": "#1",
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
3
|
||||
]
|
||||
},
|
||||
"east": {
|
||||
"texture": "#1",
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
4,
|
||||
3
|
||||
]
|
||||
},
|
||||
"south": {
|
||||
"texture": "#1",
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
3
|
||||
]
|
||||
},
|
||||
"west": {
|
||||
"texture": "#1",
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
4,
|
||||
3
|
||||
]
|
||||
},
|
||||
"up": {
|
||||
"texture": "#1",
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
4
|
||||
]
|
||||
},
|
||||
"down": {
|
||||
"texture": "#1",
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
4
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
13,
|
||||
13,
|
||||
6
|
||||
],
|
||||
"to": [
|
||||
14,
|
||||
16,
|
||||
10
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"texture": "#1",
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
3
|
||||
]
|
||||
},
|
||||
"east": {
|
||||
"texture": "#1",
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
4,
|
||||
3
|
||||
]
|
||||
},
|
||||
"south": {
|
||||
"texture": "#1",
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
3
|
||||
]
|
||||
},
|
||||
"west": {
|
||||
"texture": "#1",
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
4,
|
||||
3
|
||||
]
|
||||
},
|
||||
"up": {
|
||||
"texture": "#1",
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
4
|
||||
]
|
||||
},
|
||||
"down": {
|
||||
"texture": "#1",
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
4
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
9,
|
||||
14,
|
||||
6
|
||||
],
|
||||
"to": [
|
||||
13,
|
||||
15,
|
||||
10
|
||||
],
|
||||
"faces": {
|
||||
"up": {
|
||||
"texture": "#2",
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
4,
|
||||
3
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
7,
|
||||
12,
|
||||
2
|
||||
],
|
||||
"to": [
|
||||
9,
|
||||
14,
|
||||
4
|
||||
],
|
||||
"rotation": {
|
||||
"origin": [
|
||||
8,
|
||||
8,
|
||||
8
|
||||
],
|
||||
"axis": "y",
|
||||
"angle": 22.5
|
||||
},
|
||||
"faces": {
|
||||
"north": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
2,
|
||||
2
|
||||
]
|
||||
},
|
||||
"east": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
2,
|
||||
0,
|
||||
4,
|
||||
2
|
||||
]
|
||||
},
|
||||
"south": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
4,
|
||||
0,
|
||||
6,
|
||||
2
|
||||
]
|
||||
},
|
||||
"west": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
6,
|
||||
0,
|
||||
8,
|
||||
2
|
||||
]
|
||||
},
|
||||
"up": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
0,
|
||||
2,
|
||||
2,
|
||||
4
|
||||
]
|
||||
},
|
||||
"down": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
2,
|
||||
2
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
1,
|
||||
12,
|
||||
6
|
||||
],
|
||||
"to": [
|
||||
3,
|
||||
14,
|
||||
8
|
||||
],
|
||||
"rotation": {
|
||||
"origin": [
|
||||
8,
|
||||
8,
|
||||
8
|
||||
],
|
||||
"axis": "y",
|
||||
"angle": -22.5
|
||||
},
|
||||
"faces": {
|
||||
"north": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
2,
|
||||
2
|
||||
]
|
||||
},
|
||||
"east": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
2,
|
||||
0,
|
||||
4,
|
||||
2
|
||||
]
|
||||
},
|
||||
"south": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
4,
|
||||
0,
|
||||
6,
|
||||
2
|
||||
]
|
||||
},
|
||||
"west": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
6,
|
||||
0,
|
||||
8,
|
||||
2
|
||||
]
|
||||
},
|
||||
"up": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
0,
|
||||
2,
|
||||
2,
|
||||
4
|
||||
]
|
||||
},
|
||||
"down": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
2,
|
||||
2
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
7.5,
|
||||
14,
|
||||
2.5
|
||||
],
|
||||
"to": [
|
||||
8.5,
|
||||
15,
|
||||
3.5
|
||||
],
|
||||
"rotation": {
|
||||
"origin": [
|
||||
8,
|
||||
8,
|
||||
8
|
||||
],
|
||||
"axis": "y",
|
||||
"angle": 22.5
|
||||
},
|
||||
"faces": {
|
||||
"north": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
1
|
||||
]
|
||||
},
|
||||
"east": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
2,
|
||||
0,
|
||||
3,
|
||||
1
|
||||
]
|
||||
},
|
||||
"south": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
4,
|
||||
0,
|
||||
5,
|
||||
1
|
||||
]
|
||||
},
|
||||
"west": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
6,
|
||||
0,
|
||||
7,
|
||||
1
|
||||
]
|
||||
},
|
||||
"up": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
0,
|
||||
2,
|
||||
1,
|
||||
3
|
||||
]
|
||||
},
|
||||
"down": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
1
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
1.5,
|
||||
14,
|
||||
6.5
|
||||
],
|
||||
"to": [
|
||||
2.5,
|
||||
15,
|
||||
7.5
|
||||
],
|
||||
"rotation": {
|
||||
"origin": [
|
||||
8,
|
||||
8,
|
||||
8
|
||||
],
|
||||
"axis": "y",
|
||||
"angle": -22.5
|
||||
},
|
||||
"faces": {
|
||||
"north": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
1
|
||||
]
|
||||
},
|
||||
"east": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
2,
|
||||
0,
|
||||
3,
|
||||
1
|
||||
]
|
||||
},
|
||||
"south": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
4,
|
||||
0,
|
||||
5,
|
||||
1
|
||||
]
|
||||
},
|
||||
"west": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
6,
|
||||
0,
|
||||
7,
|
||||
1
|
||||
]
|
||||
},
|
||||
"up": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
0,
|
||||
2,
|
||||
1,
|
||||
3
|
||||
]
|
||||
},
|
||||
"down": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
1
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
4,
|
||||
12,
|
||||
5
|
||||
],
|
||||
"to": [
|
||||
6,
|
||||
14,
|
||||
7
|
||||
],
|
||||
"rotation": {
|
||||
"origin": [
|
||||
8,
|
||||
8,
|
||||
8
|
||||
],
|
||||
"axis": "y",
|
||||
"angle": 22.5
|
||||
},
|
||||
"faces": {
|
||||
"north": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
2,
|
||||
2
|
||||
]
|
||||
},
|
||||
"east": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
2,
|
||||
0,
|
||||
4,
|
||||
2
|
||||
]
|
||||
},
|
||||
"south": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
4,
|
||||
0,
|
||||
6,
|
||||
2
|
||||
]
|
||||
},
|
||||
"west": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
6,
|
||||
0,
|
||||
8,
|
||||
2
|
||||
]
|
||||
},
|
||||
"up": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
0,
|
||||
2,
|
||||
2,
|
||||
4
|
||||
]
|
||||
},
|
||||
"down": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
2,
|
||||
2
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
4.5,
|
||||
14,
|
||||
5.5
|
||||
],
|
||||
"to": [
|
||||
5.5,
|
||||
15,
|
||||
6.5
|
||||
],
|
||||
"rotation": {
|
||||
"origin": [
|
||||
8,
|
||||
8,
|
||||
8
|
||||
],
|
||||
"axis": "y",
|
||||
"angle": 22.5
|
||||
},
|
||||
"faces": {
|
||||
"north": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
1
|
||||
]
|
||||
},
|
||||
"east": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
2,
|
||||
0,
|
||||
3,
|
||||
1
|
||||
]
|
||||
},
|
||||
"south": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
4,
|
||||
0,
|
||||
5,
|
||||
1
|
||||
]
|
||||
},
|
||||
"west": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
6,
|
||||
0,
|
||||
7,
|
||||
1
|
||||
]
|
||||
},
|
||||
"up": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
0,
|
||||
2,
|
||||
1,
|
||||
3
|
||||
]
|
||||
},
|
||||
"down": {
|
||||
"texture": "#3",
|
||||
"uv": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
1
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
1,
|
||||
12.01,
|
||||
2
|
||||
],
|
||||
"to": [
|
||||
7,
|
||||
12.16,
|
||||
11
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"texture": "#paper"
|
||||
},
|
||||
"east": {
|
||||
"texture": "#paper"
|
||||
},
|
||||
"south": {
|
||||
"texture": "#paper"
|
||||
},
|
||||
"west": {
|
||||
"texture": "#paper"
|
||||
},
|
||||
"up": {
|
||||
"texture": "#paper"
|
||||
},
|
||||
"down": {
|
||||
"texture": "#paper"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"render_type": "minecraft:cutout"
|
||||
}
|
||||
@@ -7,6 +7,14 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"linking.link-disarm": {
|
||||
"category": "player",
|
||||
"sounds": [{"name": "rivencraft:linking/link-disarm"}]
|
||||
},
|
||||
"linking.link-intra": {
|
||||
"category": "player",
|
||||
"sounds": [{"name": "rivencraft:linking/link-intra"}]
|
||||
},
|
||||
"linking.link-fissure": {
|
||||
"category": "player",
|
||||
"sounds": [{"name": "rivencraft:linking/link-fissure"}]
|
||||
|
||||
|
Before Width: | Height: | Size: 868 B |
|
After Width: | Height: | Size: 311 B |
|
After Width: | Height: | Size: 304 B |
|
Before Width: | Height: | Size: 747 B |
|
Before Width: | Height: | Size: 616 B |
|
Before Width: | Height: | Size: 715 B |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 288 B |
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"parent": "rivencraft:progression/link_panel",
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "rivencraft:descriptive_book"
|
||||
},
|
||||
"title": {
|
||||
"translate": "advancement.rivencraft.bind_age.title"
|
||||
},
|
||||
"description": {
|
||||
"translate": "advancement.rivencraft.bind_age.description"
|
||||
}
|
||||
},
|
||||
"criteria": {
|
||||
"completed": {
|
||||
"trigger": "minecraft:impossible"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"parent": "rivencraft:progression/page",
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "rivencraft:writing_desk"
|
||||
},
|
||||
"title": {
|
||||
"translate": "advancement.rivencraft.copy_page.title"
|
||||
},
|
||||
"description": {
|
||||
"translate": "advancement.rivencraft.copy_page.description"
|
||||
}
|
||||
},
|
||||
"criteria": {
|
||||
"completed": {
|
||||
"trigger": "minecraft:impossible"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"parent": "rivencraft:progression/bind_age",
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "rivencraft:descriptive_book"
|
||||
},
|
||||
"title": {
|
||||
"translate": "advancement.rivencraft.enter_age.title"
|
||||
},
|
||||
"description": {
|
||||
"translate": "advancement.rivencraft.enter_age.description"
|
||||
},
|
||||
"frame": "goal"
|
||||
},
|
||||
"criteria": {
|
||||
"completed": {
|
||||
"trigger": "minecraft:impossible"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"parent": "rivencraft:progression/root",
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "rivencraft:ink_mixer"
|
||||
},
|
||||
"title": {
|
||||
"translate": "advancement.rivencraft.link_panel.title"
|
||||
},
|
||||
"description": {
|
||||
"translate": "advancement.rivencraft.link_panel.description"
|
||||
}
|
||||
},
|
||||
"criteria": {
|
||||
"completed": {
|
||||
"trigger": "minecraft:impossible"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"parent": "rivencraft:progression/root",
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "rivencraft:linking_book"
|
||||
},
|
||||
"title": {
|
||||
"translate": "advancement.rivencraft.linking_book.title"
|
||||
},
|
||||
"description": {
|
||||
"translate": "advancement.rivencraft.linking_book.description"
|
||||
}
|
||||
},
|
||||
"criteria": {
|
||||
"completed": {
|
||||
"trigger": "minecraft:inventory_changed",
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"items": "rivencraft:linking_book"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"parent": "rivencraft:progression/linking_book",
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "rivencraft:book_receptacle"
|
||||
},
|
||||
"title": {
|
||||
"translate": "advancement.rivencraft.open_portal.title"
|
||||
},
|
||||
"description": {
|
||||
"translate": "advancement.rivencraft.open_portal.description"
|
||||
},
|
||||
"frame": "goal"
|
||||
},
|
||||
"criteria": {
|
||||
"completed": {
|
||||
"trigger": "minecraft:impossible"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"parent": "rivencraft:progression/root",
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "rivencraft:page"
|
||||
},
|
||||
"title": {
|
||||
"translate": "advancement.rivencraft.page.title"
|
||||
},
|
||||
"description": {
|
||||
"translate": "advancement.rivencraft.page.description"
|
||||
}
|
||||
},
|
||||
"criteria": {
|
||||
"completed": {
|
||||
"trigger": "minecraft:inventory_changed",
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"items": "rivencraft:page"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"display": {
|
||||
"icon": {
|
||||
"id": "rivencraft:descriptive_book"
|
||||
},
|
||||
"title": {
|
||||
"translate": "advancement.rivencraft.root.title"
|
||||
},
|
||||
"description": {
|
||||
"translate": "advancement.rivencraft.root.description"
|
||||
},
|
||||
"background": "minecraft:textures/gui/advancements/backgrounds/stone.png",
|
||||
"show_toast": false,
|
||||
"announce_to_chat": false
|
||||
},
|
||||
"criteria": {
|
||||
"completed": {
|
||||
"trigger": "minecraft:tick"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"parent": "minecraft:recipes/root",
|
||||
"criteria": {
|
||||
"has_material": {
|
||||
"trigger": "minecraft:inventory_changed",
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"items": "minecraft:iron_ingot"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"has_the_recipe": {
|
||||
"trigger": "minecraft:recipe_unlocked",
|
||||
"conditions": {
|
||||
"recipe": "rivencraft:book_binder"
|
||||
}
|
||||
}
|
||||
},
|
||||
"requirements": [
|
||||
[
|
||||
"has_material",
|
||||
"has_the_recipe"
|
||||
]
|
||||
],
|
||||
"rewards": {
|
||||
"recipes": [
|
||||
"rivencraft:book_binder"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"parent": "minecraft:recipes/root",
|
||||
"criteria": {
|
||||
"has_material": {
|
||||
"trigger": "minecraft:inventory_changed",
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"items": "minecraft:glass_bottle"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"has_the_recipe": {
|
||||
"trigger": "minecraft:recipe_unlocked",
|
||||
"conditions": {
|
||||
"recipe": "rivencraft:ink_mixer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"requirements": [
|
||||
[
|
||||
"has_material",
|
||||
"has_the_recipe"
|
||||
]
|
||||
],
|
||||
"rewards": {
|
||||
"recipes": [
|
||||
"rivencraft:ink_mixer"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"parent": "minecraft:recipes/root",
|
||||
"criteria": {
|
||||
"has_material": {
|
||||
"trigger": "minecraft:inventory_changed",
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"items": "minecraft:leather"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"has_the_recipe": {
|
||||
"trigger": "minecraft:recipe_unlocked",
|
||||
"conditions": {
|
||||
"recipe": "rivencraft:notebook"
|
||||
}
|
||||
}
|
||||
},
|
||||
"requirements": [
|
||||
[
|
||||
"has_material",
|
||||
"has_the_recipe"
|
||||
]
|
||||
],
|
||||
"rewards": {
|
||||
"recipes": [
|
||||
"rivencraft:notebook"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"parent": "minecraft:recipes/root",
|
||||
"criteria": {
|
||||
"has_material": {
|
||||
"trigger": "minecraft:inventory_changed",
|
||||
"conditions": {
|
||||
"items": [
|
||||
{
|
||||
"items": "minecraft:feather"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"has_the_recipe": {
|
||||
"trigger": "minecraft:recipe_unlocked",
|
||||
"conditions": {
|
||||
"recipe": "rivencraft:writing_desk"
|
||||
}
|
||||
}
|
||||
},
|
||||
"requirements": [
|
||||
[
|
||||
"has_material",
|
||||
"has_the_recipe"
|
||||
]
|
||||
],
|
||||
"rewards": {
|
||||
"recipes": [
|
||||
"rivencraft:writing_desk"
|
||||
]
|
||||
}
|
||||
}
|
||||