Compare commits

...
1 Commits
Author SHA1 Message Date
dnorberg c39ebd78ba Add Milestone 7 progression advancements
Build / build (push) Canceled after 0s
2026-10-19 04:57:15 -04:00
23 changed files with 543 additions and 7 deletions

No files matched your search

+40
View File
@@ -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.
+1 -1
View File
@@ -36,7 +36,7 @@ mod_name=Rivencraft
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. # 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 mod_license=LGPL-3.0-or-later
# The mod version. Rivencraft uses the x.y.z.b policy documented in README.md. # The mod version. Rivencraft uses the x.y.z.b policy documented in README.md.
mod_version=0.6.5.2 mod_version=0.6.6.0
immersive_portals_version=6.0.7 immersive_portals_version=6.0.7
terrablender_version=4.1.0.8 terrablender_version=4.1.0.8
serene_seasons_version_id=B6nTjh4b serene_seasons_version_id=B6nTjh4b
@@ -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; package net.dnorberg.rivencraft.block;
import net.dnorberg.rivencraft.advancement.ProgressionAdvancements;
import com.mojang.serialization.MapCodec; import com.mojang.serialization.MapCodec;
import net.dnorberg.rivencraft.block.entity.BookReceptacleBlockEntity; import net.dnorberg.rivencraft.block.entity.BookReceptacleBlockEntity;
import net.dnorberg.rivencraft.portal.CrystalPortalNetwork; import net.dnorberg.rivencraft.portal.CrystalPortalNetwork;
@@ -117,6 +118,8 @@ public final class BookReceptacleBlock extends BaseEntityBlock {
server, pos, plan, destination)); server, pos, plan, destination));
} }
level.playSound(null, pos, SoundEvents.ENCHANTMENT_TABLE_USE, SoundSource.BLOCKS, 1, 1); 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; return ItemInteractionResult.SUCCESS;
} }
@@ -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);
}
}
@@ -1,5 +1,6 @@
package net.dnorberg.rivencraft.item; package net.dnorberg.rivencraft.item;
import net.dnorberg.rivencraft.advancement.ProgressionAdvancements;
import java.util.List; import java.util.List;
import net.dnorberg.rivencraft.book.AgeReference; import net.dnorberg.rivencraft.book.AgeReference;
import net.dnorberg.rivencraft.book.AgeVisitSavedData; import net.dnorberg.rivencraft.book.AgeVisitSavedData;
@@ -86,6 +87,7 @@ public final class DescriptiveBookItem extends Item {
} }
} }
LinkVisualEffects.play(sourceLevel, sourcePosition, player, properties, portal); LinkVisualEffects.play(sourceLevel, sourcePosition, player, properties, portal);
ProgressionAdvancements.awardAgeTravel(player);
player.displayClientMessage(Component.translatable( player.displayClientMessage(Component.translatable(
createReturnLink ? "message.rivencraft.descriptive_book.linked_first" : "message.rivencraft.descriptive_book.linked", createReturnLink ? "message.rivencraft.descriptive_book.linked_first" : "message.rivencraft.descriptive_book.linked",
age.name() age.name()
@@ -1,5 +1,6 @@
package net.dnorberg.rivencraft.item; package net.dnorberg.rivencraft.item;
import net.dnorberg.rivencraft.advancement.ProgressionAdvancements;
import java.util.List; import java.util.List;
import net.dnorberg.rivencraft.book.LinkTarget; import net.dnorberg.rivencraft.book.LinkTarget;
import net.dnorberg.rivencraft.book.AgeRegistrySavedData; import net.dnorberg.rivencraft.book.AgeRegistrySavedData;
@@ -64,6 +65,7 @@ public final class LinkingBookItem extends Item {
: LinkingService.travel(player, target, properties); : LinkingService.travel(player, target, properties);
if (result == LinkingService.LinkResult.SUCCESS) { if (result == LinkingService.LinkResult.SUCCESS) {
LinkVisualEffects.play(sourceLevel, sourcePosition, player, properties, portal); LinkVisualEffects.play(sourceLevel, sourcePosition, player, properties, portal);
ProgressionAdvancements.awardAgeTravel(player);
player.displayClientMessage(Component.translatable("message.rivencraft.linking_book.linked"), true); player.displayClientMessage(Component.translatable("message.rivencraft.linking_book.linked"), true);
return InteractionResultHolder.success(stack); return InteractionResultHolder.success(stack);
} }
@@ -1,5 +1,6 @@
package net.dnorberg.rivencraft.menu; package net.dnorberg.rivencraft.menu;
import net.dnorberg.rivencraft.advancement.ProgressionAdvancements;
import java.util.Optional; import java.util.Optional;
import net.dnorberg.rivencraft.block.entity.BookBinderBlockEntity; import net.dnorberg.rivencraft.block.entity.BookBinderBlockEntity;
import net.dnorberg.rivencraft.item.PageItem; 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.PageContent;
import net.dnorberg.rivencraft.writing.WritingGrammar; import net.dnorberg.rivencraft.writing.WritingGrammar;
import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container; import net.minecraft.world.Container;
import net.minecraft.world.SimpleContainer; import net.minecraft.world.SimpleContainer;
import net.minecraft.world.entity.player.Inventory; 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()) { if (!moveItemStackTo(remainder, PLAYER_START, slots.size(), true) || !remainder.isEmpty()) {
return ItemStack.EMPTY; return ItemStack.EMPTY;
} }
binder.consumeAssembly(); if (!binder.consumeAssembly()) return ItemStack.EMPTY;
if (player instanceof ServerPlayer serverPlayer)
ProgressionAdvancements.award(serverPlayer, ProgressionAdvancements.BIND_AGE);
refreshResult(); refreshResult();
return book; return book;
} }
@@ -284,7 +288,11 @@ public final class BookBinderMenu extends AbstractContainerMenu {
return clientSide ? hasItem() : stillValid(player) && binder.canAssemble(); return clientSide ? hasItem() : stillValid(player) && binder.canAssemble();
} }
@Override public void onTake(Player player, ItemStack stack) { @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); super.onTake(player, stack);
} }
} }
@@ -1,10 +1,12 @@
package net.dnorberg.rivencraft.menu; package net.dnorberg.rivencraft.menu;
import net.dnorberg.rivencraft.advancement.ProgressionAdvancements;
import net.dnorberg.rivencraft.block.entity.InkMixerBlockEntity; import net.dnorberg.rivencraft.block.entity.InkMixerBlockEntity;
import net.dnorberg.rivencraft.item.PageItem; import net.dnorberg.rivencraft.item.PageItem;
import net.dnorberg.rivencraft.registry.ModItems; import net.dnorberg.rivencraft.registry.ModItems;
import net.dnorberg.rivencraft.registry.ModMenus; import net.dnorberg.rivencraft.registry.ModMenus;
import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.Container; import net.minecraft.world.Container;
import net.minecraft.world.SimpleContainer; import net.minecraft.world.SimpleContainer;
import net.minecraft.world.entity.player.Inventory; 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()) if (!moveItemStackTo(remainder, PLAYER_START, slots.size(), true) || !remainder.isEmpty())
return ItemStack.EMPTY; return ItemStack.EMPTY;
if (!mixer.consumeLinkPanelMaterials()) return ItemStack.EMPTY; if (!mixer.consumeLinkPanelMaterials()) return ItemStack.EMPTY;
if (player instanceof ServerPlayer serverPlayer)
ProgressionAdvancements.award(serverPlayer, ProgressionAdvancements.LINK_PANEL);
refreshResult(); refreshResult();
return output; return output;
} }
@@ -148,7 +152,11 @@ public final class InkMixerMenu extends AbstractContainerMenu {
return clientSide ? hasItem() : stillValid(player) && mixer.canCreateLinkPanel(); return clientSide ? hasItem() : stillValid(player) && mixer.canCreateLinkPanel();
} }
@Override public void onTake(Player player, ItemStack stack) { @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); super.onTake(player, stack);
} }
} }
@@ -1,5 +1,6 @@
package net.dnorberg.rivencraft.menu; package net.dnorberg.rivencraft.menu;
import net.dnorberg.rivencraft.advancement.ProgressionAdvancements;
import net.dnorberg.rivencraft.block.entity.WritingDeskBlockEntity; import net.dnorberg.rivencraft.block.entity.WritingDeskBlockEntity;
import net.dnorberg.rivencraft.item.PageItem; import net.dnorberg.rivencraft.item.PageItem;
import net.dnorberg.rivencraft.registry.ModDataComponents; 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()) { if (!moveItemStackTo(movingCopy, PLAYER_START, slots.size(), true) || !movingCopy.isEmpty()) {
return ItemStack.EMPTY; return ItemStack.EMPTY;
} }
blockEntity.consumeCopyMaterials(); if (!blockEntity.consumeCopyMaterials()) return ItemStack.EMPTY;
if (player instanceof ServerPlayer serverPlayer)
ProgressionAdvancements.award(serverPlayer, ProgressionAdvancements.COPY_PAGE);
refreshOutput(); refreshOutput();
return copy; return copy;
} }
@@ -434,7 +437,11 @@ public final class WritingDeskMenu extends AbstractContainerMenu {
return clientSide ? hasItem() : WritingDeskMenu.this.stillValid(player) && canCopy(); return clientSide ? hasItem() : WritingDeskMenu.this.stillValid(player) && canCopy();
} }
@Override public void onTake(Player player, ItemStack stack) { @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); super.onTake(player, stack);
} }
} }
@@ -369,5 +369,21 @@
"symbol.rivencraft.corrupted.medium": "Corrupted Page", "symbol.rivencraft.corrupted.medium": "Corrupted Page",
"symbol.rivencraft.corrupted.medium.description": "The writing shifts beyond comprehension.", "symbol.rivencraft.corrupted.medium.description": "The writing shifts beyond comprehension.",
"symbol.rivencraft.corrupted.small": "Corrupted Page", "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."
} }
@@ -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"
]
}
}