Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9044835ec5 | ||
|
|
7ae41da23e |
No files matched your search
@@ -118,6 +118,11 @@ book or page is placed on it; this in-world interaction has no JEI guide.
|
||||
With Create installed, Crystal Blocks and Book Receptacles have an optional Ponder scene
|
||||
showing crystal-frame construction, book activation, and a parrot crossing the portal. Use the Ponder shortcut
|
||||
while hovering either item; Rivencraft does not require Create to run.
|
||||
Crystal frames may stand vertically or lie flat: a 3×3 Crystal Block ring with
|
||||
an empty center forms a horizontal portal when a Book Receptacle is attached to
|
||||
the top or underside of one of its blocks. Insert a bound Linking or Descriptive
|
||||
Book to activate it; the book remains visible on the receptacle. A Linking Book aimed at its
|
||||
own dimension still opens a visual portal, but travel requires Intra Linking.
|
||||
|
||||
Craft a Book Binder from three iron ingots and five planks. Put leather or an
|
||||
empty Notebook in the left cover slot. Enter a title, then place a Link Panel
|
||||
|
||||
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 497 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 73 KiB |
@@ -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.
|
||||
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.7.2
|
||||
mod_version=0.6.8.4
|
||||
immersive_portals_version=6.0.7
|
||||
terrablender_version=4.1.0.8
|
||||
serene_seasons_version_id=pHEgQQUE
|
||||
|
||||
@@ -77,7 +77,7 @@ public final class BookReceptacleBlock extends BaseEntityBlock {
|
||||
}
|
||||
@Override public BlockState getStateForPlacement(BlockPlaceContext context) {
|
||||
Direction face = context.getClickedFace();
|
||||
if (face == Direction.DOWN || !context.getLevel().getBlockState(
|
||||
if (!context.getLevel().getBlockState(
|
||||
context.getClickedPos().relative(face.getOpposite())).is(ModBlocks.CRYSTAL_BLOCK.get())) return null;
|
||||
return defaultBlockState().setValue(FACING, face);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
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.block.BookReceptacleBlock;
|
||||
import net.dnorberg.rivencraft.block.entity.BookReceptacleBlockEntity;
|
||||
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.blockentity.BlockEntityRenderer;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
/** Legacy's almost-closed book, oriented to the receptacle's mounting face. */
|
||||
public final class BookReceptacleRenderer implements BlockEntityRenderer<BookReceptacleBlockEntity> {
|
||||
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 BookReceptacleRenderer(BlockEntityRendererProvider.Context context) {
|
||||
model = new BookModel(context.bakeLayer(ModelLayers.BOOK));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(BookReceptacleBlockEntity receptacle, float partialTick, PoseStack poseStack,
|
||||
MultiBufferSource buffers, int packedLight, int packedOverlay) {
|
||||
if (receptacle.getBook().isEmpty()) return;
|
||||
|
||||
poseStack.pushPose();
|
||||
poseStack.translate(0.5, 0.5, 0.5);
|
||||
Direction facing = receptacle.getBlockState().getValue(BookReceptacleBlock.FACING);
|
||||
switch (facing) {
|
||||
case UP -> {
|
||||
// Rotate the whole assembled top-mounted receptacle/book pair
|
||||
// around the block center, matching the x=90 block model.
|
||||
poseStack.mulPose(Axis.ZP.rotationDegrees(180));
|
||||
poseStack.mulPose(Axis.XP.rotationDegrees(-90));
|
||||
poseStack.mulPose(Axis.YP.rotationDegrees(90));
|
||||
// Preserve the local half-turn that seats the book in its slot.
|
||||
poseStack.mulPose(Axis.ZP.rotationDegrees(180));
|
||||
}
|
||||
case DOWN -> {
|
||||
// The ceiling-mounted model is the inverted top assembly.
|
||||
poseStack.mulPose(Axis.XP.rotationDegrees(-90));
|
||||
poseStack.mulPose(Axis.YP.rotationDegrees(90));
|
||||
poseStack.mulPose(Axis.ZP.rotationDegrees(180));
|
||||
}
|
||||
case NORTH -> poseStack.mulPose(Axis.YP.rotationDegrees(-90));
|
||||
case SOUTH -> poseStack.mulPose(Axis.YP.rotationDegrees(90));
|
||||
case EAST -> poseStack.mulPose(Axis.YP.rotationDegrees(180));
|
||||
case WEST -> { }
|
||||
}
|
||||
poseStack.scale(0.8F, 0.8F, 0.8F);
|
||||
model.setupAnim(0.0F, 0.1F, 0.1F, 0.005F);
|
||||
ResourceLocation texture = receptacle.getBook().is(ModItems.DESCRIPTIVE_BOOK.get())
|
||||
? DESCRIPTIVE_TEXTURE : LINKING_TEXTURE;
|
||||
VertexConsumer vertices = buffers.getBuffer(RenderType.entitySolid(texture));
|
||||
model.render(poseStack, vertices, packedLight, packedOverlay, -1);
|
||||
poseStack.popPose();
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,7 @@ public final class ClientEvents {
|
||||
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.BOOK_RECEPTACLE.get(), BookReceptacleRenderer::new);
|
||||
event.registerBlockEntityRenderer(ModBlockEntities.WRITING_DESK.get(), WritingDeskRenderer::new);
|
||||
event.registerBlockEntityRenderer(ModBlockEntities.STAR_FISSURE.get(), StarFissureRenderer::new);
|
||||
}
|
||||
|
||||
@@ -25,12 +25,14 @@ import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.dnorberg.rivencraft.item.PageItem;
|
||||
import net.dnorberg.rivencraft.book.LinkProperties;
|
||||
import net.dnorberg.rivencraft.registry.ModDataComponents;
|
||||
import net.dnorberg.rivencraft.registry.ModCreativeTabs;
|
||||
import net.dnorberg.rivencraft.writing.InkModifierIngredients;
|
||||
import net.dnorberg.rivencraft.writing.PageContent;
|
||||
import net.dnorberg.rivencraft.writing.SymbolReference;
|
||||
import net.dnorberg.rivencraft.registry.ModItems;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.MutableComponent;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
@@ -63,6 +65,11 @@ public final class RivencraftJeiPlugin implements IModPlugin {
|
||||
case UNRESOLVED_SYMBOL -> "unresolved:" + content.symbol().orElseThrow().id();
|
||||
};
|
||||
});
|
||||
registration.registerSubtypeInterpreter(ModItems.SYMBOL_PORTFOLIO.get(),
|
||||
(IIngredientSubtypeInterpreter<ItemStack>) (stack, context) -> {
|
||||
Component name = stack.get(DataComponents.CUSTOM_NAME);
|
||||
return name == null ? "empty" : name.getString();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -72,6 +79,7 @@ public final class RivencraftJeiPlugin implements IModPlugin {
|
||||
var connection = Minecraft.getInstance().getConnection();
|
||||
if (connection != null) {
|
||||
pages.addAll(PageItem.creativeSymbolPages(connection.registryAccess()));
|
||||
pages.addAll(ModCreativeTabs.creativeSymbolPortfolios(connection.registryAccess()));
|
||||
} else {
|
||||
PageItem.bundledCreativeSymbols().stream().map(PageItem::createBundledCreativePage)
|
||||
.forEach(pages::add);
|
||||
@@ -84,6 +92,8 @@ public final class RivencraftJeiPlugin implements IModPlugin {
|
||||
// JEI discovers the base Page item as a blank stack; only authored pages belong in its list.
|
||||
runtime.getIngredientVisibility().hideIngredients(VanillaTypes.ITEM_STACK,
|
||||
List.of(new ItemStack(ModItems.PAGE.get())), List.of(UidContext.Ingredient));
|
||||
runtime.getIngredientVisibility().hideIngredients(VanillaTypes.ITEM_STACK,
|
||||
List.of(new ItemStack(ModItems.SYMBOL_PORTFOLIO.get())), List.of(UidContext.Ingredient));
|
||||
var connection = Minecraft.getInstance().getConnection();
|
||||
if (connection == null) return;
|
||||
Set<ResourceLocation> known = runtime.getIngredientManager().getAllItemStacks().stream()
|
||||
@@ -98,6 +108,19 @@ public final class RivencraftJeiPlugin implements IModPlugin {
|
||||
if (!missing.isEmpty()) {
|
||||
runtime.getIngredientManager().addIngredientsAtRuntime(VanillaTypes.ITEM_STACK, missing);
|
||||
}
|
||||
Set<String> knownPortfolios = runtime.getIngredientManager().getAllItemStacks().stream()
|
||||
.filter(stack -> stack.is(ModItems.SYMBOL_PORTFOLIO.get()))
|
||||
.map(stack -> stack.get(DataComponents.CUSTOM_NAME))
|
||||
.filter(java.util.Objects::nonNull)
|
||||
.map(Component::getString)
|
||||
.collect(Collectors.toSet());
|
||||
List<ItemStack> missingPortfolios = ModCreativeTabs.creativeSymbolPortfolios(connection.registryAccess())
|
||||
.stream()
|
||||
.filter(stack -> !knownPortfolios.contains(stack.getHoverName().getString()))
|
||||
.toList();
|
||||
if (!missingPortfolios.isEmpty()) {
|
||||
runtime.getIngredientManager().addIngredientsAtRuntime(VanillaTypes.ITEM_STACK, missingPortfolios);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -6,6 +6,9 @@ import net.dnorberg.rivencraft.block.CrystalPortalBlock;
|
||||
import net.dnorberg.rivencraft.block.entity.BookReceptacleBlockEntity;
|
||||
import net.dnorberg.rivencraft.block.entity.CrystalPortalBlockEntity;
|
||||
import net.dnorberg.rivencraft.book.LinkTarget;
|
||||
import net.dnorberg.rivencraft.book.LinkProperties;
|
||||
import net.dnorberg.rivencraft.book.LinkingService;
|
||||
import net.dnorberg.rivencraft.registry.ModDataComponents;
|
||||
import net.dnorberg.rivencraft.portal.CrystalPortalNetwork;
|
||||
import net.dnorberg.rivencraft.portal.CrystalPortalSurfaces;
|
||||
import net.dnorberg.rivencraft.portal.PortalTravelCooldown;
|
||||
@@ -19,7 +22,11 @@ import net.minecraft.gametest.framework.GameTestHelper;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.item.context.BlockPlaceContext;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.neoforged.neoforge.gametest.GameTestHolder;
|
||||
import net.neoforged.neoforge.gametest.PrefixGameTestTemplate;
|
||||
|
||||
@@ -154,13 +161,54 @@ public final class CrystalPortalGameTests {
|
||||
BlockPos holderPos = helper.absolutePos(new BlockPos(0, 2, 1));
|
||||
level.setBlock(holderPos, ModBlocks.BOOK_RECEPTACLE.get().defaultBlockState()
|
||||
.setValue(BookReceptacleBlock.FACING, Direction.UP), 3);
|
||||
((BookReceptacleBlockEntity) level.getBlockEntity(holderPos))
|
||||
.setBook(new ItemStack(ModItems.LINKING_BOOK.get()));
|
||||
LinkTarget destination = new LinkTarget(GlobalPos.of(level.dimension(), centerTarget(helper)), 0, 0);
|
||||
ItemStack book = new ItemStack(ModItems.LINKING_BOOK.get());
|
||||
book.set(ModDataComponents.LINK_TARGET.get(), destination);
|
||||
BookReceptacleBlockEntity holder = (BookReceptacleBlockEntity) level.getBlockEntity(holderPos);
|
||||
holder.setBook(book);
|
||||
holder.setDestination(destination);
|
||||
helper.assertTrue(CrystalPortalNetwork.activate(level, holderPos, planLoaded(level, holderPos)),
|
||||
"The horizontal crystal opening must activate");
|
||||
helper.assertValueEqual(level.getBlockState(helper.absolutePos(new BlockPos(1, 1, 1)))
|
||||
.getValue(CrystalPortalBlock.AXIS), Direction.Axis.Y,
|
||||
"The fallback surface must face up and down");
|
||||
helper.assertValueEqual(CrystalPortalSurfaces.group(level, planLoaded(level, holderPos)).getFirst().normal(),
|
||||
Direction.UP, "The horizontal opening must provide an upward-facing destination view");
|
||||
var player = helper.makeMockServerPlayerInLevel();
|
||||
helper.assertValueEqual(LinkingService.travelPortal(player, destination, LinkProperties.NONE),
|
||||
LinkingService.LinkResult.INTRA_LINKING_REQUIRED,
|
||||
"A normal book may show a portal but must not link within its own dimension");
|
||||
helper.assertTrue(level.getBlockState(helper.absolutePos(new BlockPos(1, 1, 1)))
|
||||
.is(ModBlocks.CRYSTAL_PORTAL.get()) && !holder.getBook().isEmpty(),
|
||||
"Rejected intra-link travel must leave the visual portal and its book intact");
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void undersideReceptaclePlacesAndActivatesHorizontalFrame(GameTestHelper helper) {
|
||||
var level = helper.getLevel();
|
||||
for (int x = 0; x < 3; x++) for (int z = 0; z < 3; z++) {
|
||||
level.setBlock(helper.absolutePos(new BlockPos(x, 2, z)),
|
||||
x == 1 && z == 1 ? Blocks.AIR.defaultBlockState()
|
||||
: ModBlocks.CRYSTAL_BLOCK.get().defaultBlockState(), 3);
|
||||
}
|
||||
BlockPos support = helper.absolutePos(new BlockPos(0, 2, 1));
|
||||
BlockPos holderPos = support.below();
|
||||
BlockPlaceContext context = new BlockPlaceContext(level, null, InteractionHand.MAIN_HAND,
|
||||
new ItemStack(ModItems.BOOK_RECEPTACLE.get()),
|
||||
new BlockHitResult(Vec3.atCenterOf(support).add(0, -0.5, 0),
|
||||
Direction.DOWN, support, false));
|
||||
var placed = ModBlocks.BOOK_RECEPTACLE.get().getStateForPlacement(context);
|
||||
helper.assertTrue(placed != null && placed.getValue(BookReceptacleBlock.FACING) == Direction.DOWN,
|
||||
"Clicking the underside of a Crystal Block must place a downward-facing receptacle");
|
||||
level.setBlock(holderPos, placed, 3);
|
||||
((BookReceptacleBlockEntity) level.getBlockEntity(holderPos))
|
||||
.setBook(new ItemStack(ModItems.LINKING_BOOK.get()));
|
||||
helper.assertTrue(CrystalPortalNetwork.activate(level, holderPos, planLoaded(level, holderPos)),
|
||||
"A ceiling-mounted receptacle must activate its horizontal crystal frame");
|
||||
helper.assertValueEqual(level.getBlockState(helper.absolutePos(new BlockPos(1, 2, 1)))
|
||||
.getValue(CrystalPortalBlock.AXIS), Direction.Axis.Y,
|
||||
"The underside-mounted portal surface remains horizontal");
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package net.dnorberg.rivencraft.gametest;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.dnorberg.rivencraft.item.CollationFolderItem;
|
||||
import net.dnorberg.rivencraft.item.PageItem;
|
||||
import net.dnorberg.rivencraft.item.SymbolPortfolioItem;
|
||||
import net.dnorberg.rivencraft.registry.ModCreativeTabs;
|
||||
import net.dnorberg.rivencraft.registry.ModDataComponents;
|
||||
import net.dnorberg.rivencraft.registry.ModItems;
|
||||
import net.dnorberg.rivencraft.writing.NotebookContents;
|
||||
import net.dnorberg.rivencraft.writing.PageContent;
|
||||
import net.minecraft.gametest.framework.GameTest;
|
||||
import net.minecraft.gametest.framework.GameTestHelper;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.neoforged.neoforge.gametest.GameTestHolder;
|
||||
import net.neoforged.neoforge.gametest.PrefixGameTestTemplate;
|
||||
|
||||
@GameTestHolder(Rivencraft.MOD_ID)
|
||||
@PrefixGameTestTemplate(false)
|
||||
public final class SymbolPortfolioGameTests {
|
||||
private SymbolPortfolioGameTests() {}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void portfoliosCoverEverySymbolCategoryWithoutTruncation(GameTestHelper helper) {
|
||||
var registries = helper.getLevel().registryAccess();
|
||||
List<ItemStack> creative = ModCreativeTabs.creativeSymbolPortfolios(registries);
|
||||
helper.assertTrue(!creative.isEmpty(), "Creative tab must include prefilled portfolios");
|
||||
for (ResourceLocation category : PageItem.bundledCreativeCategories()) {
|
||||
List<ItemStack> expected = PageItem.creativeSymbolPages(registries, category);
|
||||
List<ItemStack> volumes = SymbolPortfolioItem.createVolumes(category, expected);
|
||||
helper.assertTrue(!volumes.isEmpty(), "Missing portfolio for " + category);
|
||||
List<ItemStack> actual = new ArrayList<>();
|
||||
for (ItemStack volume : volumes) {
|
||||
helper.assertTrue(volume.is(ModItems.SYMBOL_PORTFOLIO.get())
|
||||
&& CollationFolderItem.isCollection(volume)
|
||||
&& creative.stream().anyMatch(candidate ->
|
||||
candidate.getHoverName().getString().equals(volume.getHoverName().getString())),
|
||||
"Portfolio must be an editable creative collection for " + category);
|
||||
NotebookContents contents = volume.get(ModDataComponents.NOTEBOOK_CONTENTS.get());
|
||||
helper.assertTrue(contents != null && contents.pageCount() > 0
|
||||
&& contents.pageCount() <= NotebookContents.MAX_PAGES,
|
||||
"Each portfolio volume must contain valid pages");
|
||||
helper.assertTrue(contents.pages().stream().allMatch(page ->
|
||||
PageItem.content(page).kind() == PageContent.Kind.SYMBOL),
|
||||
"Portfolios must contain symbol pages, not Link Panels");
|
||||
actual.addAll(contents.pages());
|
||||
}
|
||||
helper.assertTrue(actual.size() == expected.size(), "Portfolio omitted pages in " + category);
|
||||
for (int index = 0; index < expected.size(); index++) {
|
||||
helper.assertTrue(ItemStack.isSameItemSameComponents(actual.get(index), expected.get(index)),
|
||||
"Portfolio pages must match the category tab in " + category);
|
||||
}
|
||||
}
|
||||
helper.succeed();
|
||||
}
|
||||
}
|
||||
@@ -13,11 +13,13 @@ public final class CollationFolderItem extends NotebookItem {
|
||||
}
|
||||
|
||||
public static boolean isCollection(ItemStack stack) {
|
||||
return stack.is(ModItems.NOTEBOOK.get()) || stack.is(ModItems.COLLATION_FOLDER.get());
|
||||
return stack.is(ModItems.NOTEBOOK.get()) || stack.is(ModItems.COLLATION_FOLDER.get())
|
||||
|| stack.is(ModItems.SYMBOL_PORTFOLIO.get());
|
||||
}
|
||||
|
||||
public static int capacity(ItemStack stack) {
|
||||
return stack.is(ModItems.COLLATION_FOLDER.get()) ? MAX_PAGES
|
||||
: stack.is(ModItems.NOTEBOOK.get()) ? NotebookContents.MAX_PAGES : 0;
|
||||
: stack.is(ModItems.NOTEBOOK.get()) || stack.is(ModItems.SYMBOL_PORTFOLIO.get())
|
||||
? NotebookContents.MAX_PAGES : 0;
|
||||
}
|
||||
}
|
||||
@@ -39,8 +39,7 @@ public class NotebookItem extends Item {
|
||||
NotebookContents contents = stack.getOrDefault(
|
||||
ModDataComponents.NOTEBOOK_CONTENTS.get(), NotebookContents.EMPTY);
|
||||
tooltip.add(Component.translatable("item.rivencraft.notebook.pages",
|
||||
contents.pageCount(), stack.is(net.dnorberg.rivencraft.registry.ModItems.COLLATION_FOLDER.get())
|
||||
? CollationFolderItem.MAX_PAGES : NotebookContents.MAX_PAGES)
|
||||
contents.pageCount(), CollationFolderItem.capacity(stack))
|
||||
.withStyle(ChatFormatting.GRAY));
|
||||
if (contents.unresolvedCount() > 0) {
|
||||
tooltip.add(Component.translatable("item.rivencraft.notebook.unresolved",
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package net.dnorberg.rivencraft.item;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import net.dnorberg.rivencraft.registry.ModDataComponents;
|
||||
import net.dnorberg.rivencraft.registry.ModItems;
|
||||
import net.dnorberg.rivencraft.writing.NotebookContents;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
/** Non-craftable, prefilled Legacy-style collections of one symbol-page category. */
|
||||
public final class SymbolPortfolioItem extends NotebookItem {
|
||||
public SymbolPortfolioItem(Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
public static List<ItemStack> createVolumes(ResourceLocation category, List<ItemStack> pages) {
|
||||
if (pages.isEmpty()) return List.of();
|
||||
int capacity = NotebookContents.MAX_PAGES;
|
||||
int volumeCount = (pages.size() + capacity - 1) / capacity;
|
||||
List<ItemStack> volumes = new ArrayList<>(volumeCount);
|
||||
for (int volume = 0; volume < volumeCount; volume++) {
|
||||
ItemStack portfolio = new ItemStack(ModItems.SYMBOL_PORTFOLIO.get());
|
||||
int first = volume * capacity;
|
||||
int last = Math.min(first + capacity, pages.size());
|
||||
portfolio.set(ModDataComponents.NOTEBOOK_CONTENTS.get(),
|
||||
new NotebookContents(pages.subList(first, last)));
|
||||
Component categoryName = Component.translatableWithFallback(
|
||||
"itemGroup.rivencraft.pages." + category.getPath(), category.getPath());
|
||||
portfolio.set(DataComponents.CUSTOM_NAME, volumeCount == 1
|
||||
? Component.translatable("item.rivencraft.symbol_portfolio.named", categoryName)
|
||||
: Component.translatable("item.rivencraft.symbol_portfolio.volume",
|
||||
categoryName, volume + 1, volumeCount));
|
||||
volumes.add(portfolio);
|
||||
}
|
||||
return List.copyOf(volumes);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,9 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.dnorberg.rivencraft.item.PageItem;
|
||||
import net.dnorberg.rivencraft.item.SymbolPortfolioItem;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
@@ -28,6 +31,7 @@ public final class ModCreativeTabs {
|
||||
output.accept(ModItems.LINKING_BOOK.get());
|
||||
output.accept(ModItems.NOTEBOOK.get());
|
||||
output.accept(ModItems.COLLATION_FOLDER.get());
|
||||
creativeSymbolPortfolios(parameters.holders()).forEach(output::accept);
|
||||
output.accept(ModItems.SEALED_NOTEBOOK.get());
|
||||
output.accept(ModItems.INK_VIAL.get());
|
||||
output.accept(ModItems.WRITING_DESK.get());
|
||||
@@ -59,14 +63,28 @@ public final class ModCreativeTabs {
|
||||
public static final Map<ResourceLocation, DeferredHolder<CreativeModeTab, CreativeModeTab>> SYMBOL_PAGES =
|
||||
registerSymbolPageTabs();
|
||||
|
||||
private static Map<ResourceLocation, DeferredHolder<CreativeModeTab, CreativeModeTab>> registerSymbolPageTabs() {
|
||||
/** Prefilled portfolios mirror the symbol-page tabs, including data-pack and modded pages. */
|
||||
public static List<ItemStack> creativeSymbolPortfolios(HolderLookup.Provider registries) {
|
||||
List<ItemStack> portfolios = new ArrayList<>();
|
||||
for (ResourceLocation category : orderedSymbolCategories()) {
|
||||
portfolios.addAll(SymbolPortfolioItem.createVolumes(category,
|
||||
PageItem.creativeSymbolPages(registries, category)));
|
||||
}
|
||||
return List.copyOf(portfolios);
|
||||
}
|
||||
|
||||
private static List<ResourceLocation> orderedSymbolCategories() {
|
||||
List<ResourceLocation> categories = new ArrayList<>(PageItem.bundledCreativeCategories());
|
||||
categories.sort(Comparator.comparingInt((ResourceLocation id) -> {
|
||||
int index = CATEGORY_ORDER.indexOf(id.getPath());
|
||||
return index < 0 ? CATEGORY_ORDER.size() : index;
|
||||
}).thenComparing(ResourceLocation::toString));
|
||||
return List.copyOf(categories);
|
||||
}
|
||||
|
||||
private static Map<ResourceLocation, DeferredHolder<CreativeModeTab, CreativeModeTab>> registerSymbolPageTabs() {
|
||||
Map<ResourceLocation, DeferredHolder<CreativeModeTab, CreativeModeTab>> tabs = new LinkedHashMap<>();
|
||||
for (ResourceLocation category : categories) {
|
||||
for (ResourceLocation category : orderedSymbolCategories()) {
|
||||
String key = "rivencraft_pages_" + category.getNamespace() + "_" + category.getPath().replace('/', '_');
|
||||
tabs.put(category, CREATIVE_TABS.register(key, () -> CreativeModeTab.builder()
|
||||
.title(Component.translatableWithFallback("itemGroup.rivencraft.pages." + category.getPath(),
|
||||
|
||||
@@ -6,6 +6,7 @@ import net.dnorberg.rivencraft.item.LinkingBookItem;
|
||||
import net.dnorberg.rivencraft.item.PageItem;
|
||||
import net.dnorberg.rivencraft.item.NotebookItem;
|
||||
import net.dnorberg.rivencraft.item.CollationFolderItem;
|
||||
import net.dnorberg.rivencraft.item.SymbolPortfolioItem;
|
||||
import net.dnorberg.rivencraft.item.SealedNotebookItem;
|
||||
import net.dnorberg.rivencraft.item.InkVialItem;
|
||||
import net.minecraft.world.item.Item;
|
||||
@@ -39,6 +40,8 @@ public final class ModItems {
|
||||
);
|
||||
public static final DeferredItem<CollationFolderItem> COLLATION_FOLDER = ITEMS.registerItem(
|
||||
"collation_folder", CollationFolderItem::new, new Item.Properties().stacksTo(1));
|
||||
public static final DeferredItem<SymbolPortfolioItem> SYMBOL_PORTFOLIO = ITEMS.registerItem(
|
||||
"symbol_portfolio", SymbolPortfolioItem::new, new Item.Properties().stacksTo(1));
|
||||
public static final DeferredItem<SealedNotebookItem> SEALED_NOTEBOOK = ITEMS.registerItem(
|
||||
"sealed_notebook", SealedNotebookItem::new, new Item.Properties().stacksTo(16));
|
||||
public static final DeferredItem<InkVialItem> INK_VIAL = ITEMS.registerItem(
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"variants":{"facing=north":{"model":"rivencraft:block/book_receptacle","y":180},"facing=east":{"model":"rivencraft:block/book_receptacle","y":270},"facing=south":{"model":"rivencraft:block/book_receptacle"},"facing=west":{"model":"rivencraft:block/book_receptacle","y":90},"facing=up":{"model":"rivencraft:block/book_receptacle","x":270},"facing=down":{"model":"rivencraft:block/book_receptacle","x":90}}}
|
||||
{"variants":{"facing=north":{"model":"rivencraft:block/book_receptacle","y":180},"facing=east":{"model":"rivencraft:block/book_receptacle","y":270},"facing=south":{"model":"rivencraft:block/book_receptacle"},"facing=west":{"model":"rivencraft:block/book_receptacle","y":90},"facing=up":{"model":"rivencraft:block/book_receptacle","x":90},"facing=down":{"model":"rivencraft:block/book_receptacle","x":270,"y":180}}}
|
||||
@@ -22,6 +22,9 @@
|
||||
"item.rivencraft.page.unresolved": "Unresolved Symbol Page",
|
||||
"item.rivencraft.notebook": "Notebook",
|
||||
"item.rivencraft.collation_folder": "Collation Folder",
|
||||
"item.rivencraft.symbol_portfolio": "Symbol Portfolio",
|
||||
"item.rivencraft.symbol_portfolio.named": "%s Portfolio",
|
||||
"item.rivencraft.symbol_portfolio.volume": "%s Portfolio %s/%s",
|
||||
"item.rivencraft.sealed_notebook": "Sealed Notebook",
|
||||
"block.rivencraft.link_modifier": "Link Modifier",
|
||||
"container.rivencraft.link_modifier": "Link Modifier",
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"parent": "minecraft:item/generated",
|
||||
"textures": { "layer0": "rivencraft:item/symbol_portfolio" }
|
||||
}
|
||||
|
After Width: | Height: | Size: 328 B |