Public Access
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee905cab68 | ||
|
|
5c08f391a2 | ||
|
|
b8d26a9036 |
No files matched your search
@@ -26,12 +26,12 @@ release V0.6.6.0.
|
||||
| --- | --- | --- | --- |
|
||||
| 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 | 19.64.0.455 | Separate update issue; verify recipe categories and GUI discovery. |
|
||||
| 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 compile target | 10.1.0.9 | Separate update issue; the user's Prism instance already contains 10.1.0.9, but that is not a recorded compatibility pass. |
|
||||
| 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. |
|
||||
|
||||
|
||||
@@ -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.
|
||||
+3
-3
@@ -22,7 +22,7 @@ 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.6.6
|
||||
mod_version=0.6.6.9
|
||||
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.
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package net.dnorberg.rivencraft.block;
|
||||
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import net.dnorberg.rivencraft.block.entity.LinkModifierBlockEntity;
|
||||
import net.dnorberg.rivencraft.menu.LinkModifierMenu;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.Containers;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.SimpleMenuProvider;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.context.BlockPlaceContext;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.BaseEntityBlock;
|
||||
import net.minecraft.world.level.block.RenderShape;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.StateDefinition;
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
||||
import net.minecraft.world.level.block.state.properties.DirectionProperty;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
|
||||
public final class LinkModifierBlock extends BaseEntityBlock {
|
||||
public static final MapCodec<LinkModifierBlock> CODEC = simpleCodec(LinkModifierBlock::new);
|
||||
public static final DirectionProperty FACING = BlockStateProperties.HORIZONTAL_FACING;
|
||||
|
||||
public LinkModifierBlock(Properties properties) {
|
||||
super(properties);
|
||||
registerDefaultState(stateDefinition.any().setValue(FACING, Direction.NORTH));
|
||||
}
|
||||
|
||||
@Override protected MapCodec<? extends BaseEntityBlock> codec() { return CODEC; }
|
||||
@Override protected void createBlockStateDefinition(StateDefinition.Builder<net.minecraft.world.level.block.Block,
|
||||
BlockState> builder) { builder.add(FACING); }
|
||||
@Override public BlockState getStateForPlacement(BlockPlaceContext context) {
|
||||
return defaultBlockState().setValue(FACING, context.getHorizontalDirection().getOpposite());
|
||||
}
|
||||
@Override protected RenderShape getRenderShape(BlockState state) { return RenderShape.MODEL; }
|
||||
@Override public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
|
||||
return new LinkModifierBlockEntity(pos, state);
|
||||
}
|
||||
@Override protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos,
|
||||
Player player, BlockHitResult hit) {
|
||||
if (!level.isClientSide && player instanceof ServerPlayer serverPlayer
|
||||
&& level.getBlockEntity(pos) instanceof LinkModifierBlockEntity modifier) {
|
||||
serverPlayer.openMenu(new SimpleMenuProvider(
|
||||
(id, inventory, ignored) -> new LinkModifierMenu(id, inventory, modifier),
|
||||
Component.translatable("container.rivencraft.link_modifier")),
|
||||
buffer -> buffer.writeBlockPos(pos));
|
||||
}
|
||||
return InteractionResult.sidedSuccess(level.isClientSide);
|
||||
}
|
||||
@Override protected void onRemove(BlockState state, Level level, BlockPos pos,
|
||||
BlockState replacement, boolean moving) {
|
||||
if (!state.is(replacement.getBlock()) && !level.isClientSide
|
||||
&& level.getBlockEntity(pos) instanceof LinkModifierBlockEntity modifier) {
|
||||
Containers.dropContents(level, pos, modifier);
|
||||
}
|
||||
super.onRemove(state, level, pos, replacement, moving);
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import net.dnorberg.rivencraft.registry.ModBlockEntities;
|
||||
import net.dnorberg.rivencraft.registry.ModDataComponents;
|
||||
import net.dnorberg.rivencraft.registry.ModItems;
|
||||
import net.dnorberg.rivencraft.writing.NotebookContents;
|
||||
import net.dnorberg.rivencraft.item.CollationFolderItem;
|
||||
import net.dnorberg.rivencraft.writing.OrderedWriting;
|
||||
import net.dnorberg.rivencraft.writing.PageContent;
|
||||
import net.dnorberg.rivencraft.writing.WritingGrammar;
|
||||
@@ -96,7 +97,7 @@ public final class BookBinderBlockEntity extends BlockEntity implements Containe
|
||||
case COVER -> validCover(stack);
|
||||
case LINK_PANEL -> stack.is(ModItems.PAGE.get())
|
||||
&& PageItem.content(stack).kind() == PageContent.Kind.LINK_PANEL;
|
||||
case NOTEBOOK -> stack.is(ModItems.NOTEBOOK.get());
|
||||
case NOTEBOOK -> CollationFolderItem.isCollection(stack);
|
||||
default -> slot >= FIRST_PAGE && slot < SIZE && validPage(stack);
|
||||
};
|
||||
}
|
||||
@@ -296,7 +297,7 @@ public final class BookBinderBlockEntity extends BlockEntity implements Containe
|
||||
|
||||
/** Import a Notebook from either its stored slot or the player's cursor. */
|
||||
public ItemStack importNotebookFromStack(ItemStack notebook, int index) {
|
||||
if (!notebook.is(ModItems.NOTEBOOK.get()) || notebook.getCount() != 1) return ItemStack.EMPTY;
|
||||
if (!CollationFolderItem.isCollection(notebook) || notebook.getCount() != 1) return ItemStack.EMPTY;
|
||||
List<ItemStack> pages = notebook.getOrDefault(ModDataComponents.NOTEBOOK_CONTENTS.get(), NotebookContents.EMPTY).pages();
|
||||
if (pages.isEmpty()) return ItemStack.EMPTY;
|
||||
int end = nextPageIndex();
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package net.dnorberg.rivencraft.block.entity;
|
||||
|
||||
import net.dnorberg.rivencraft.registry.ModBlockEntities;
|
||||
import net.dnorberg.rivencraft.registry.ModItems;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.core.NonNullList;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.ContainerHelper;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
/** One persisted book slot; edits are performed by the server menu. */
|
||||
public final class LinkModifierBlockEntity extends BlockEntity implements Container {
|
||||
private NonNullList<ItemStack> items = NonNullList.withSize(1, ItemStack.EMPTY);
|
||||
|
||||
public LinkModifierBlockEntity(BlockPos pos, BlockState state) {
|
||||
super(ModBlockEntities.LINK_MODIFIER.get(), pos, state);
|
||||
}
|
||||
|
||||
@Override public int getContainerSize() { return 1; }
|
||||
@Override public boolean isEmpty() { return items.getFirst().isEmpty(); }
|
||||
@Override public ItemStack getItem(int slot) { return items.get(slot); }
|
||||
@Override public ItemStack removeItem(int slot, int amount) {
|
||||
ItemStack removed = ContainerHelper.removeItem(items, slot, amount);
|
||||
if (!removed.isEmpty()) setChanged();
|
||||
return removed;
|
||||
}
|
||||
@Override public ItemStack removeItemNoUpdate(int slot) {
|
||||
ItemStack removed = ContainerHelper.takeItem(items, slot);
|
||||
if (!removed.isEmpty()) setChanged();
|
||||
return removed;
|
||||
}
|
||||
@Override public void setItem(int slot, ItemStack stack) {
|
||||
items.set(slot, stack.copyWithCount(Math.min(1, stack.getCount())));
|
||||
setChanged();
|
||||
}
|
||||
@Override public boolean canPlaceItem(int slot, ItemStack stack) {
|
||||
return slot == 0 && (stack.is(ModItems.LINKING_BOOK.get())
|
||||
|| stack.is(ModItems.DESCRIPTIVE_BOOK.get()));
|
||||
}
|
||||
@Override public boolean stillValid(Player player) {
|
||||
return level != null && level.isLoaded(worldPosition) && level.getBlockEntity(worldPosition) == this
|
||||
&& player.distanceToSqr(worldPosition.getX() + 0.5, worldPosition.getY() + 0.5,
|
||||
worldPosition.getZ() + 0.5) <= 64.0;
|
||||
}
|
||||
@Override public void clearContent() {
|
||||
items = NonNullList.withSize(1, ItemStack.EMPTY);
|
||||
setChanged();
|
||||
}
|
||||
@Override protected void loadAdditional(CompoundTag tag, HolderLookup.Provider registries) {
|
||||
super.loadAdditional(tag, registries);
|
||||
items = NonNullList.withSize(1, ItemStack.EMPTY);
|
||||
ContainerHelper.loadAllItems(tag, items, registries);
|
||||
}
|
||||
@Override protected void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) {
|
||||
super.saveAdditional(tag, registries);
|
||||
ContainerHelper.saveAllItems(tag, items, registries);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package net.dnorberg.rivencraft.block.entity;
|
||||
|
||||
import net.dnorberg.rivencraft.item.InkVialItem;
|
||||
import net.dnorberg.rivencraft.item.PageItem;
|
||||
import net.dnorberg.rivencraft.item.CollationFolderItem;
|
||||
import net.dnorberg.rivencraft.writing.InkVialExchange;
|
||||
import net.dnorberg.rivencraft.writing.WritingInkService;
|
||||
import net.dnorberg.rivencraft.writing.PageContent;
|
||||
@@ -87,7 +88,7 @@ public final class WritingDeskBlockEntity extends BlockEntity implements Contain
|
||||
worldPosition.getZ() + 0.5) <= 64.0;
|
||||
}
|
||||
@Override public boolean canPlaceItem(int slot, ItemStack stack) {
|
||||
if (slot < COLLECTIONS) return stack.is(ModItems.NOTEBOOK.get())
|
||||
if (slot < COLLECTIONS) return CollationFolderItem.isCollection(stack)
|
||||
&& stack.getOrDefault(ModDataComponents.NOTEBOOK_CONTENTS.get(), NotebookContents.EMPTY)
|
||||
.pages().stream().noneMatch(page -> PageItem.content(page).kind() == PageContent.Kind.LINK_PANEL)
|
||||
|| stack.is(ModItems.PAGE.get())
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package net.dnorberg.rivencraft.book;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.AtomicMoveNotSupportedException;
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.world.level.storage.LevelResource;
|
||||
|
||||
/** Retires an exact Age and moves its unloaded region data out of the live world. */
|
||||
public final class AgeDeletionService {
|
||||
public enum Result { RETIRED, ALREADY_DEAD, OCCUPIED, UNLOAD_FAILED, STORAGE_FAILED }
|
||||
|
||||
private AgeDeletionService() { }
|
||||
|
||||
public static Result retire(MinecraftServer server, AgeDefinition age) {
|
||||
AgeRegistrySavedData registry = AgeRegistrySavedData.get(server.overworld());
|
||||
if (registry.isDead(age.id())) return Result.ALREADY_DEAD;
|
||||
var level = server.getLevel(AgeDimensionService.dimensionKey(age));
|
||||
if (level != null && !level.players().isEmpty()) return Result.OCCUPIED;
|
||||
if (AgeDimensionService.unload(server, age) == AgeDimensionService.UnloadResult.FAILED)
|
||||
return Result.UNLOAD_FAILED;
|
||||
|
||||
Path world = server.getWorldPath(LevelResource.ROOT).toAbsolutePath().normalize();
|
||||
Path ageRoot = world.resolve("dimensions/rivencraft/age").normalize();
|
||||
Path data = world.resolve("dimensions")
|
||||
.resolve(age.dimensionId().getNamespace())
|
||||
.resolve(age.dimensionId().getPath()).normalize();
|
||||
// Exact Rivencraft Age only; never move the shared age/ directory.
|
||||
if (!data.startsWith(ageRoot) || data.equals(ageRoot)
|
||||
|| !"rivencraft".equals(age.dimensionId().getNamespace())
|
||||
|| !age.dimensionId().getPath().startsWith("age/")) return Result.STORAGE_FAILED;
|
||||
try {
|
||||
if (Files.isSymbolicLink(world.resolve("dimensions"))
|
||||
|| Files.isSymbolicLink(world.resolve("dimensions/rivencraft"))
|
||||
|| Files.isSymbolicLink(ageRoot) || Files.isSymbolicLink(data)) return Result.STORAGE_FAILED;
|
||||
if (Files.exists(data, LinkOption.NOFOLLOW_LINKS)) {
|
||||
Path archive = world.getParent().resolve(world.getFileName() + "-retired-ages")
|
||||
.resolve(age.id().toString());
|
||||
if (Files.exists(archive, LinkOption.NOFOLLOW_LINKS)) return Result.STORAGE_FAILED;
|
||||
Files.createDirectories(archive.getParent());
|
||||
try { Files.move(data, archive, StandardCopyOption.ATOMIC_MOVE); }
|
||||
catch (AtomicMoveNotSupportedException exception) { Files.move(data, archive); }
|
||||
Rivencraft.LOGGER.warn("Retired Age {} data moved from {} to {}", age.id(), data, archive);
|
||||
}
|
||||
} catch (IOException exception) {
|
||||
Rivencraft.LOGGER.error("Cannot archive data for Age {}", age.id(), exception);
|
||||
return Result.STORAGE_FAILED;
|
||||
}
|
||||
registry.markDead(age.id());
|
||||
return Result.RETIRED;
|
||||
}
|
||||
}
|
||||
@@ -74,6 +74,7 @@ public final class AgeDimensionService {
|
||||
DynamicDimensionLoadCallback.register((server, loader) -> {
|
||||
ServerLevel overworld = server.overworld();
|
||||
for (AgeDefinition age : AgeRegistrySavedData.get(overworld).definitions()) {
|
||||
if (AgeRegistrySavedData.get(overworld).isDead(age.id())) continue;
|
||||
if (!canLoadProfile(server, age)) continue;
|
||||
ServerLevel level = loader.loadDynamicDimension(
|
||||
age.dimensionId(),
|
||||
@@ -96,6 +97,7 @@ public final class AgeDimensionService {
|
||||
public static Optional<LinkTarget> resolve(ServerPlayer player, AgeReference reference,
|
||||
OrderedWriting writing) {
|
||||
AgeDefinition definition = AgeRegistrySavedData.resolveOrCreate(player.serverLevel(), reference, writing);
|
||||
if (AgeRegistrySavedData.get(player.serverLevel()).isDead(definition.id())) return Optional.empty();
|
||||
ServerLevel level = ensureLoaded(player.getServer(), definition);
|
||||
if (level == null) {
|
||||
return Optional.empty();
|
||||
@@ -122,6 +124,7 @@ public final class AgeDimensionService {
|
||||
}
|
||||
|
||||
public static ServerLevel ensureLoaded(MinecraftServer server, AgeDefinition definition) {
|
||||
if (AgeRegistrySavedData.get(server.overworld()).isDead(definition.id())) return null;
|
||||
ResourceKey<Level> key = dimensionKey(definition);
|
||||
ServerLevel existing = server.getLevel(key);
|
||||
if (existing != null) {
|
||||
|
||||
@@ -23,6 +23,24 @@ public final class AgePersistenceGameTests {
|
||||
private AgePersistenceGameTests() {
|
||||
}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void retiredAgeIdentitySurvivesSaveAndCannotLoad(GameTestHelper helper) {
|
||||
UUID id = UUID.randomUUID();
|
||||
AgeReference reference = new AgeReference(id, "Retired " + id);
|
||||
AgeDefinition age = AgeRegistrySavedData.resolveOrCreate(helper.getLevel(), reference);
|
||||
AgeRegistrySavedData registry = AgeRegistrySavedData.get(helper.getLevel());
|
||||
helper.assertTrue(registry.markDead(id), "The first retirement must set the tombstone");
|
||||
helper.assertTrue(!registry.markDead(id), "Retirement must be idempotent");
|
||||
helper.assertTrue(AgeDimensionService.ensureLoaded(helper.getLevel().getServer(), age) == null,
|
||||
"A retired Age must never load from an old book");
|
||||
AgeRegistrySavedData restored = AgeRegistrySavedData.load(
|
||||
registry.save(new CompoundTag(), helper.getLevel().registryAccess()),
|
||||
helper.getLevel().registryAccess());
|
||||
helper.assertTrue(restored.isDead(id) && restored.find(id).isPresent(),
|
||||
"Both the retired flag and reserved Age identity must survive reload");
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void authoredPlanRetainsResolvedMeaningAndAgeIdentity(GameTestHelper helper) {
|
||||
OrderedWriting writing = new OrderedWriting(List.of(
|
||||
|
||||
@@ -5,6 +5,9 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.List;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import net.minecraft.nbt.StringTag;
|
||||
import net.dnorberg.rivencraft.writing.OrderedWriting;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
@@ -21,6 +24,7 @@ import net.neoforged.fml.ModList;
|
||||
public final class AgeRegistrySavedData extends SavedData {
|
||||
private static final String DATA_NAME = "rivencraft_ages";
|
||||
private static final String AGES_TAG = "Ages";
|
||||
private static final String DEAD_AGES_TAG = "DeadAges";
|
||||
private static final String ID_TAG = "Id";
|
||||
private static final String NAME_TAG = "Name";
|
||||
private static final String SEED_TAG = "Seed";
|
||||
@@ -50,6 +54,7 @@ public final class AgeRegistrySavedData extends SavedData {
|
||||
);
|
||||
|
||||
private final Map<UUID, AgeDefinition> ages = new HashMap<>();
|
||||
private final Set<UUID> deadAges = new HashSet<>();
|
||||
|
||||
public static AgeRegistrySavedData get(ServerLevel level) {
|
||||
return level.getServer().overworld().getDataStorage().computeIfAbsent(FACTORY, DATA_NAME);
|
||||
@@ -110,6 +115,16 @@ public final class AgeRegistrySavedData extends SavedData {
|
||||
return List.copyOf(ages.values());
|
||||
}
|
||||
|
||||
public boolean isDead(UUID ageId) { return deadAges.contains(ageId); }
|
||||
|
||||
/** Keep the ID and definition reserved so an old book cannot regenerate a retired Age. */
|
||||
public boolean markDead(UUID ageId) {
|
||||
if (!ages.containsKey(ageId)) return false;
|
||||
boolean changed = deadAges.add(ageId);
|
||||
if (changed) setDirty();
|
||||
return changed;
|
||||
}
|
||||
|
||||
public AgeDefinition setSpawn(UUID ageId, BlockPos spawn) {
|
||||
AgeDefinition existing = ages.get(ageId);
|
||||
if (existing == null) {
|
||||
@@ -161,6 +176,11 @@ public final class AgeRegistrySavedData extends SavedData {
|
||||
|
||||
static AgeRegistrySavedData load(CompoundTag tag, HolderLookup.Provider registries) {
|
||||
AgeRegistrySavedData data = new AgeRegistrySavedData();
|
||||
ListTag deadTags = tag.getList(DEAD_AGES_TAG, StringTag.TAG_STRING);
|
||||
for (int index = 0; index < deadTags.size(); index++) {
|
||||
try { data.deadAges.add(UUID.fromString(deadTags.getString(index))); }
|
||||
catch (IllegalArgumentException ignored) { }
|
||||
}
|
||||
ListTag serializedAges = tag.getList(AGES_TAG, CompoundTag.TAG_COMPOUND);
|
||||
for (int index = 0; index < serializedAges.size(); index++) {
|
||||
CompoundTag entry = serializedAges.getCompound(index);
|
||||
@@ -264,6 +284,9 @@ public final class AgeRegistrySavedData extends SavedData {
|
||||
serializedAges.add(entry);
|
||||
});
|
||||
tag.put(AGES_TAG, serializedAges);
|
||||
ListTag deadTags = new ListTag();
|
||||
deadAges.stream().sorted().forEach(id -> deadTags.add(StringTag.valueOf(id.toString())));
|
||||
tag.put(DEAD_AGES_TAG, deadTags);
|
||||
return tag;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import net.dnorberg.rivencraft.client.screen.NotebookScreen;
|
||||
import net.dnorberg.rivencraft.client.screen.WritingDeskScreen;
|
||||
import net.dnorberg.rivencraft.client.screen.BookBinderScreen;
|
||||
import net.dnorberg.rivencraft.client.screen.InkMixerScreen;
|
||||
import net.dnorberg.rivencraft.client.screen.LinkModifierScreen;
|
||||
import net.dnorberg.rivencraft.registry.ModMenus;
|
||||
|
||||
@EventBusSubscriber(modid = Rivencraft.MOD_ID, value = Dist.CLIENT)
|
||||
@@ -47,6 +48,7 @@ public final class ClientEvents {
|
||||
event.register(ModMenus.WRITING_DESK.get(), WritingDeskScreen::new);
|
||||
event.register(ModMenus.BOOK_BINDER.get(), BookBinderScreen::new);
|
||||
event.register(ModMenus.INK_MIXER.get(), InkMixerScreen::new);
|
||||
event.register(ModMenus.LINK_MODIFIER.get(), LinkModifierScreen::new);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
|
||||
@@ -27,6 +27,7 @@ import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.components.EditBox;
|
||||
import net.minecraft.client.gui.components.Tooltip;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.sounds.SoundEvents;
|
||||
import net.minecraft.util.FormattedCharSequence;
|
||||
@@ -48,6 +49,7 @@ public final class DescriptiveBookScreen extends Screen implements ExplorableBoo
|
||||
private static final Component EXPLORE_ICON = Component.literal("👁");
|
||||
|
||||
private final AgeReference age;
|
||||
private final Component displayName;
|
||||
private final ResourceLocation generationProfile;
|
||||
private final AgeTraits traits;
|
||||
private final OrderedWriting writing;
|
||||
@@ -68,6 +70,7 @@ public final class DescriptiveBookScreen extends Screen implements ExplorableBoo
|
||||
List<SymbolDisplayMetadata> writingSymbols) {
|
||||
super(book.getHoverName());
|
||||
age = book.get(ModDataComponents.AGE_REFERENCE.get());
|
||||
displayName = book.get(DataComponents.CUSTOM_NAME);
|
||||
generationProfile = book.get(ModDataComponents.AGE_GENERATION_PROFILE.get());
|
||||
this.traits = traits;
|
||||
writing = book.getOrDefault(ModDataComponents.BOOK_WRITING.get(), OrderedWriting.EMPTY);
|
||||
@@ -82,6 +85,7 @@ public final class DescriptiveBookScreen extends Screen implements ExplorableBoo
|
||||
List<SymbolDisplayMetadata> writingSymbols) {
|
||||
super(book.getHoverName());
|
||||
age = book.get(ModDataComponents.AGE_REFERENCE.get());
|
||||
displayName = book.get(DataComponents.CUSTOM_NAME);
|
||||
generationProfile = book.get(ModDataComponents.AGE_GENERATION_PROFILE.get());
|
||||
this.traits = traits;
|
||||
writing = book.getOrDefault(ModDataComponents.BOOK_WRITING.get(), OrderedWriting.EMPTY);
|
||||
@@ -245,7 +249,8 @@ public final class DescriptiveBookScreen extends Screen implements ExplorableBoo
|
||||
}
|
||||
return;
|
||||
}
|
||||
int nameEnd = drawWrappedWithoutShadow(graphics, Component.literal(age.name()),
|
||||
int nameEnd = drawWrappedWithoutShadow(graphics,
|
||||
displayName == null ? Component.literal(age.name()) : displayName,
|
||||
x, top + 40, 110);
|
||||
int detailsY = drawWrappedWithoutShadow(graphics,
|
||||
author == null || author.isBlank()
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
package net.dnorberg.rivencraft.client.screen;
|
||||
|
||||
import net.dnorberg.rivencraft.book.LinkProperties;
|
||||
import net.dnorberg.rivencraft.menu.LinkModifierMenu;
|
||||
import net.dnorberg.rivencraft.network.SetLinkModifierTitlePayload;
|
||||
import net.dnorberg.rivencraft.registry.ModDataComponents;
|
||||
import net.dnorberg.rivencraft.registry.ModItems;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.components.EditBox;
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.neoforged.neoforge.network.PacketDistributor;
|
||||
|
||||
public final class LinkModifierScreen extends AbstractContainerScreen<LinkModifierMenu> {
|
||||
private EditBox titleInput;
|
||||
private Button disarm;
|
||||
private Button momentum;
|
||||
private Button intra;
|
||||
private Button retire;
|
||||
private ItemStack lastBook = ItemStack.EMPTY;
|
||||
|
||||
public LinkModifierScreen(LinkModifierMenu menu, Inventory inventory, Component title) {
|
||||
super(menu, inventory, title);
|
||||
imageWidth = 176;
|
||||
imageHeight = 194;
|
||||
inventoryLabelX = 8;
|
||||
inventoryLabelY = 99;
|
||||
}
|
||||
|
||||
@Override protected void init() {
|
||||
super.init();
|
||||
titleInput = new EditBox(font, leftPos + 8, topPos + 54, 160, 14,
|
||||
Component.translatable("screen.rivencraft.link_modifier.title"));
|
||||
titleInput.setMaxLength(32);
|
||||
titleInput.setResponder(value -> PacketDistributor.sendToServer(
|
||||
new SetLinkModifierTitlePayload(menu.containerId, value)));
|
||||
addRenderableWidget(titleInput);
|
||||
disarm = addRenderableWidget(Button.builder(Component.empty(), button -> click(LinkModifierMenu.TOGGLE_DISARM))
|
||||
.bounds(leftPos + 8, topPos + 74, 76, 20).build());
|
||||
momentum = addRenderableWidget(Button.builder(Component.empty(), button -> click(LinkModifierMenu.TOGGLE_MOMENTUM))
|
||||
.bounds(leftPos + 92, topPos + 74, 76, 20).build());
|
||||
intra = addRenderableWidget(Button.builder(Component.empty(), button -> click(LinkModifierMenu.TOGGLE_INTRA))
|
||||
.bounds(leftPos + 8, topPos + 18, 64, 20).build());
|
||||
retire = addRenderableWidget(Button.builder(Component.empty(), button -> click(LinkModifierMenu.DELETE_AGE))
|
||||
.bounds(leftPos + 104, topPos + 18, 64, 20).build());
|
||||
refreshControls();
|
||||
}
|
||||
|
||||
private void click(int id) {
|
||||
if (minecraft != null && minecraft.gameMode != null) {
|
||||
minecraft.gameMode.handleInventoryButtonClick(menu.containerId, id);
|
||||
}
|
||||
}
|
||||
|
||||
@Override protected void containerTick() { refreshControls(); }
|
||||
|
||||
private void refreshControls() {
|
||||
ItemStack book = menu.book();
|
||||
if (!ItemStack.isSameItemSameComponents(book, lastBook) && !titleInput.isFocused()) {
|
||||
Component name = book.get(DataComponents.CUSTOM_NAME);
|
||||
titleInput.setValue(name == null ? "" : name.getString());
|
||||
lastBook = book.copy();
|
||||
}
|
||||
boolean present = !book.isEmpty();
|
||||
titleInput.setEditable(present);
|
||||
LinkProperties properties = book.getOrDefault(ModDataComponents.LINK_PROPERTIES.get(), LinkProperties.NONE);
|
||||
disarm.active = present;
|
||||
disarm.setMessage(Component.literal("Disarm " + (properties.has(LinkProperties.DISARM) ? "✓" : "×")));
|
||||
momentum.active = present;
|
||||
momentum.setMessage(Component.literal("Momentum " + (properties.has(LinkProperties.MAINTAIN_MOMENTUM) ? "✓" : "×")));
|
||||
intra.visible = book.is(ModItems.LINKING_BOOK.get());
|
||||
intra.setMessage(Component.literal("Intra " + (properties.has(LinkProperties.INTRA_LINKING) ? "✓" : "×")));
|
||||
retire.visible = menu.canDelete(minecraft.player);
|
||||
retire.setMessage(Component.translatable(menu.awaitingDeletionConfirmation()
|
||||
? "screen.rivencraft.link_modifier.confirm" : "screen.rivencraft.link_modifier.retire"));
|
||||
}
|
||||
|
||||
@Override protected void renderBg(GuiGraphics graphics, float partialTick, int mouseX, int mouseY) {
|
||||
graphics.fill(leftPos, topPos, leftPos + imageWidth, topPos + imageHeight, 0xFFD6C7A7);
|
||||
graphics.renderOutline(leftPos, topPos, imageWidth, imageHeight, 0xFF493C34);
|
||||
graphics.fill(leftPos + 78, topPos + 28, leftPos + 98, topPos + 48, 0xFF83785F);
|
||||
graphics.renderOutline(leftPos + 77, topPos + 27, 22, 22, 0xFF493C34);
|
||||
for (int row = 0; row < 3; row++) for (int col = 0; col < 9; col++)
|
||||
slotBackground(graphics, 8 + col * 18, 112 + row * 18);
|
||||
for (int col = 0; col < 9; col++) slotBackground(graphics, 8 + col * 18, 170);
|
||||
}
|
||||
|
||||
private void slotBackground(GuiGraphics graphics, int x, int y) {
|
||||
graphics.fill(leftPos + x - 1, topPos + y - 1, leftPos + x + 17, topPos + y + 17, 0xFF81745C);
|
||||
}
|
||||
|
||||
@Override protected void renderLabels(GuiGraphics graphics, int mouseX, int mouseY) {
|
||||
graphics.drawString(font, title, 8, 6, 0xFF332722, false);
|
||||
graphics.drawString(font, playerInventoryTitle, inventoryLabelX, inventoryLabelY, 0xFF332722, false);
|
||||
}
|
||||
|
||||
@Override public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) {
|
||||
super.render(graphics, mouseX, mouseY, partialTick);
|
||||
renderTooltip(graphics, mouseX, mouseY);
|
||||
}
|
||||
}
|
||||
@@ -182,9 +182,9 @@ public final class NotebookScreen extends AbstractContainerScreen<NotebookMenu>
|
||||
|
||||
private int displayedPageSlots() {
|
||||
if (menu.getCarried().is(ModItems.PAGE.get())) {
|
||||
return NotebookMenu.NOTEBOOK_SLOTS;
|
||||
return menu.pageCapacity();
|
||||
}
|
||||
for (int index = NotebookMenu.NOTEBOOK_SLOTS - 1; index >= 0; index--) {
|
||||
for (int index = menu.pageCapacity() - 1; index >= 0; index--) {
|
||||
if (!menu.getSlot(index).getItem().isEmpty()) {
|
||||
return index + 1;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import net.dnorberg.rivencraft.menu.WritingDeskMenu;
|
||||
import net.dnorberg.rivencraft.block.entity.WritingDeskBlockEntity;
|
||||
import net.dnorberg.rivencraft.registry.ModDataComponents;
|
||||
import net.dnorberg.rivencraft.registry.ModItems;
|
||||
import net.dnorberg.rivencraft.item.CollationFolderItem;
|
||||
import net.dnorberg.rivencraft.writing.NotebookContents;
|
||||
import net.dnorberg.rivencraft.writing.SymbolDisplayMetadata;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
@@ -234,7 +235,7 @@ public final class WritingDeskScreen extends AbstractContainerScreen<WritingDesk
|
||||
private List<ItemStack> pages(int collectionIndex) {
|
||||
ItemStack collection = menu.getSlot(collectionIndex).getItem();
|
||||
if (collection.is(ModItems.PAGE.get())) return List.of(collection);
|
||||
if (collection.is(ModItems.NOTEBOOK.get())) {
|
||||
if (CollationFolderItem.isCollection(collection)) {
|
||||
return collection.getOrDefault(ModDataComponents.NOTEBOOK_CONTENTS.get(), NotebookContents.EMPTY).pages();
|
||||
}
|
||||
return List.of();
|
||||
@@ -301,9 +302,9 @@ public final class WritingDeskScreen extends AbstractContainerScreen<WritingDesk
|
||||
|
||||
private boolean canMovePage(DisplayPage source, DisplayPage target) {
|
||||
return !source.stack().isEmpty()
|
||||
&& (menu.getSlot(source.collection()).getItem().is(ModItems.NOTEBOOK.get())
|
||||
&& (CollationFolderItem.isCollection(menu.getSlot(source.collection()).getItem())
|
||||
|| menu.getSlot(source.collection()).getItem().is(ModItems.PAGE.get()))
|
||||
&& (menu.getSlot(target.collection()).getItem().is(ModItems.NOTEBOOK.get())
|
||||
&& (CollationFolderItem.isCollection(menu.getSlot(target.collection()).getItem())
|
||||
|| menu.getSlot(target.collection()).getItem().is(ModItems.PAGE.get()))
|
||||
&& (source.collection() != target.collection() || source.index() != target.index());
|
||||
}
|
||||
@@ -317,10 +318,10 @@ public final class WritingDeskScreen extends AbstractContainerScreen<WritingDesk
|
||||
if (!slot.mayPlace(held)) return -1;
|
||||
ItemStack current = slot.getItem();
|
||||
if (current.isEmpty()) return index;
|
||||
if (index < WritingDeskBlockEntity.COLLECTIONS && current.is(ModItems.NOTEBOOK.get())) {
|
||||
if (index < WritingDeskBlockEntity.COLLECTIONS && CollationFolderItem.isCollection(current)) {
|
||||
List<ItemStack> pages = current.getOrDefault(
|
||||
ModDataComponents.NOTEBOOK_CONTENTS.get(), NotebookContents.EMPTY).pages();
|
||||
if (pages.size() < NotebookContents.MAX_PAGES || pages.stream().anyMatch(ItemStack::isEmpty))
|
||||
if (pages.size() < CollationFolderItem.capacity(current) || pages.stream().anyMatch(ItemStack::isEmpty))
|
||||
return index;
|
||||
} else if (index > WritingDeskMenu.COPY_RESULT && ItemStack.isSameItemSameComponents(current, held)
|
||||
&& current.getCount() < slot.getMaxStackSize(held)) return index;
|
||||
|
||||
@@ -112,8 +112,9 @@ public final class RivencraftCommands {
|
||||
source.sendSuccess(() -> Component.literal("Rivencraft Ages: " + ages.size()), false);
|
||||
for (AgeDefinition age : ages) {
|
||||
boolean loaded = source.getServer().getLevel(AgeDimensionService.dimensionKey(age)) != null;
|
||||
boolean retired = AgeRegistrySavedData.get(source.getLevel()).isDead(age.id());
|
||||
source.sendSuccess(() -> Component.literal(
|
||||
(loaded ? "[loaded] " : "[unloaded] ")
|
||||
(retired ? "[retired] " : loaded ? "[loaded] " : "[unloaded] ")
|
||||
+ age.name() + " — " + age.dimensionId()
|
||||
+ (age.seasonMode() == null ? "" : " (season: "
|
||||
+ age.seasonMode().name().toLowerCase(java.util.Locale.ROOT) + ")")
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package net.dnorberg.rivencraft.item;
|
||||
|
||||
import net.dnorberg.rivencraft.registry.ModItems;
|
||||
import net.dnorberg.rivencraft.writing.NotebookContents;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
/** Legacy's compact page collection, using the existing Notebook page editor. */
|
||||
public final class CollationFolderItem extends NotebookItem {
|
||||
public static final int MAX_PAGES = 27;
|
||||
|
||||
public CollationFolderItem(Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
public static boolean isCollection(ItemStack stack) {
|
||||
return stack.is(ModItems.NOTEBOOK.get()) || stack.is(ModItems.COLLATION_FOLDER.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;
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.TooltipFlag;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
public final class NotebookItem extends Item {
|
||||
public class NotebookItem extends Item {
|
||||
public NotebookItem(Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
@@ -39,7 +39,9 @@ public final class NotebookItem extends Item {
|
||||
NotebookContents contents = stack.getOrDefault(
|
||||
ModDataComponents.NOTEBOOK_CONTENTS.get(), NotebookContents.EMPTY);
|
||||
tooltip.add(Component.translatable("item.rivencraft.notebook.pages",
|
||||
contents.pageCount(), NotebookContents.MAX_PAGES).withStyle(ChatFormatting.GRAY));
|
||||
contents.pageCount(), stack.is(net.dnorberg.rivencraft.registry.ModItems.COLLATION_FOLDER.get())
|
||||
? CollationFolderItem.MAX_PAGES : NotebookContents.MAX_PAGES)
|
||||
.withStyle(ChatFormatting.GRAY));
|
||||
if (contents.unresolvedCount() > 0) {
|
||||
tooltip.add(Component.translatable("item.rivencraft.notebook.unresolved",
|
||||
contents.unresolvedCount()).withStyle(ChatFormatting.RED));
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
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.dnorberg.rivencraft.writing.SymbolCatalog;
|
||||
import net.dnorberg.rivencraft.writing.SymbolReference;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResultHolder;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
/** Opens into a 16-page Collation Folder, as Legacy's booster item did. */
|
||||
public final class SealedNotebookItem extends Item {
|
||||
private static final int PAGE_COUNT = 16;
|
||||
|
||||
public SealedNotebookItem(Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResultHolder<ItemStack> use(Level level, Player player, InteractionHand hand) {
|
||||
ItemStack sealed = player.getItemInHand(hand);
|
||||
if (level.isClientSide) return InteractionResultHolder.sidedSuccess(sealed, true);
|
||||
if (!(player instanceof ServerPlayer serverPlayer)) return InteractionResultHolder.pass(sealed);
|
||||
ItemStack folder = open(serverPlayer);
|
||||
if (folder.isEmpty()) return InteractionResultHolder.fail(sealed);
|
||||
if (!player.hasInfiniteMaterials()) sealed.shrink(1);
|
||||
if (sealed.isEmpty()) {
|
||||
player.setItemInHand(hand, folder);
|
||||
} else if (!player.getInventory().add(folder)) {
|
||||
player.drop(folder, false);
|
||||
}
|
||||
return InteractionResultHolder.success(player.getItemInHand(hand));
|
||||
}
|
||||
|
||||
static ItemStack open(ServerPlayer player) {
|
||||
// Current symbol definitions have no Legacy rank. Draw from the authored
|
||||
// symbol pool, excluding virtual block/biome pages and corrupted pages.
|
||||
List<ResourceLocation> candidates = SymbolCatalog.symbols().entrySet().stream()
|
||||
.filter(entry -> entry.getKey().getNamespace().equals("rivencraft")
|
||||
&& !entry.getKey().getPath().startsWith("block/")
|
||||
&& !entry.getKey().getPath().startsWith("biome/")
|
||||
&& !entry.getValue().category().getPath().equals("corrupted"))
|
||||
.map(java.util.Map.Entry::getKey).sorted().toList();
|
||||
if (candidates.isEmpty()) return ItemStack.EMPTY;
|
||||
List<ItemStack> pages = new ArrayList<>(PAGE_COUNT);
|
||||
for (int i = 0; i < PAGE_COUNT; i++) {
|
||||
ResourceLocation id = candidates.get(player.getRandom().nextInt(candidates.size()));
|
||||
pages.add(PageItem.createSymbolPage(new SymbolReference(id)));
|
||||
}
|
||||
ItemStack folder = new ItemStack(ModItems.COLLATION_FOLDER.get());
|
||||
folder.set(ModDataComponents.NOTEBOOK_CONTENTS.get(), new NotebookContents(pages));
|
||||
return folder;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import net.dnorberg.rivencraft.block.entity.BookBinderBlockEntity;
|
||||
import net.dnorberg.rivencraft.item.PageItem;
|
||||
import net.dnorberg.rivencraft.registry.ModDataComponents;
|
||||
import net.dnorberg.rivencraft.registry.ModItems;
|
||||
import net.dnorberg.rivencraft.item.CollationFolderItem;
|
||||
import net.dnorberg.rivencraft.registry.ModMenus;
|
||||
import net.dnorberg.rivencraft.writing.PageContent;
|
||||
import net.dnorberg.rivencraft.writing.WritingGrammar;
|
||||
@@ -172,7 +173,7 @@ public final class BookBinderMenu extends AbstractContainerMenu {
|
||||
|
||||
private boolean clickStrip(int index) {
|
||||
ItemStack carried = getCarried();
|
||||
if (carried.is(ModItems.NOTEBOOK.get())) {
|
||||
if (CollationFolderItem.isCollection(carried)) {
|
||||
ItemStack emptied = binder.importNotebookFromStack(carried, Math.min(index == 0 ? 0 : index - 1,
|
||||
binder.nextPageIndex()));
|
||||
if (emptied.isEmpty()) return false;
|
||||
@@ -227,7 +228,7 @@ public final class BookBinderMenu extends AbstractContainerMenu {
|
||||
moved = moveItemStackTo(source, PLAYER_START, slots.size(), true);
|
||||
} else if (source.is(Items.LEATHER) || BookBinderBlockEntity.validCover(source)) {
|
||||
moved = moveItemStackTo(source, BookBinderBlockEntity.COVER, BookBinderBlockEntity.COVER + 1, false);
|
||||
} else if (source.is(ModItems.NOTEBOOK.get())) {
|
||||
} else if (CollationFolderItem.isCollection(source)) {
|
||||
ItemStack emptied = binder.importNotebookFromStack(source, binder.nextPageIndex());
|
||||
if (emptied.isEmpty()) return ItemStack.EMPTY;
|
||||
sourceSlot.setByPlayer(emptied);
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
package net.dnorberg.rivencraft.menu;
|
||||
|
||||
import java.util.UUID;
|
||||
import net.dnorberg.rivencraft.block.entity.LinkModifierBlockEntity;
|
||||
import net.dnorberg.rivencraft.book.AgeDefinition;
|
||||
import net.dnorberg.rivencraft.book.AgeDeletionService;
|
||||
import net.dnorberg.rivencraft.book.AgeReference;
|
||||
import net.dnorberg.rivencraft.book.AgeRegistrySavedData;
|
||||
import net.dnorberg.rivencraft.book.LinkProperties;
|
||||
import net.dnorberg.rivencraft.registry.ModDataComponents;
|
||||
import net.dnorberg.rivencraft.registry.ModItems;
|
||||
import net.dnorberg.rivencraft.registry.ModMenus;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.SimpleContainer;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
import net.minecraft.world.inventory.DataSlot;
|
||||
import net.minecraft.world.inventory.Slot;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
/** Server-owned book edits and a two-stage, OP-only Age retirement control. */
|
||||
public final class LinkModifierMenu extends AbstractContainerMenu {
|
||||
public static final int TOGGLE_DISARM = 1;
|
||||
public static final int TOGGLE_MOMENTUM = 2;
|
||||
public static final int TOGGLE_INTRA = 3;
|
||||
public static final int DELETE_AGE = 4;
|
||||
private static final int PLAYER_START = 1;
|
||||
private static final int MAX_TITLE_LENGTH = 32;
|
||||
|
||||
private final LinkModifierBlockEntity modifier;
|
||||
private final Container bookSlot;
|
||||
private final boolean clientSide;
|
||||
private int confirmation;
|
||||
private UUID pendingAge;
|
||||
|
||||
public LinkModifierMenu(int id, Inventory inventory, RegistryFriendlyByteBuf data) {
|
||||
this(id, inventory, inventory.player.level().getBlockEntity(data.readBlockPos())
|
||||
instanceof LinkModifierBlockEntity found ? found : null);
|
||||
}
|
||||
|
||||
public LinkModifierMenu(int id, Inventory inventory, LinkModifierBlockEntity modifier) {
|
||||
super(ModMenus.LINK_MODIFIER.get(), id);
|
||||
this.modifier = modifier;
|
||||
this.bookSlot = modifier == null ? new SimpleContainer(1) : modifier;
|
||||
this.clientSide = inventory.player.level().isClientSide;
|
||||
addDataSlot(new DataSlot() {
|
||||
@Override public int get() { return confirmation; }
|
||||
@Override public void set(int value) { confirmation = value; }
|
||||
});
|
||||
addSlot(new Slot(bookSlot, 0, 80, 30) {
|
||||
@Override public boolean mayPlace(ItemStack stack) { return bookSlot.canPlaceItem(0, stack); }
|
||||
@Override public int getMaxStackSize() { return 1; }
|
||||
@Override public void setChanged() {
|
||||
super.setChanged();
|
||||
pendingAge = null;
|
||||
confirmation = 0;
|
||||
}
|
||||
});
|
||||
for (int row = 0; row < 3; row++) for (int col = 0; col < 9; col++)
|
||||
addSlot(new Slot(inventory, col + row * 9 + 9, 8 + col * 18, 112 + row * 18));
|
||||
for (int col = 0; col < 9; col++)
|
||||
addSlot(new Slot(inventory, col, 8 + col * 18, 170));
|
||||
}
|
||||
|
||||
public ItemStack book() { return bookSlot.getItem(0); }
|
||||
public boolean awaitingDeletionConfirmation() { return confirmation == 1; }
|
||||
public boolean canDelete(Player player) {
|
||||
return player.hasPermissions(2) && book().is(ModItems.DESCRIPTIVE_BOOK.get())
|
||||
&& book().has(ModDataComponents.AGE_REFERENCE.get());
|
||||
}
|
||||
|
||||
public boolean setTitle(Player player, String title) {
|
||||
if (clientSide || !stillValid(player) || title == null || title.length() > MAX_TITLE_LENGTH
|
||||
|| book().isEmpty()) return false;
|
||||
String clean = title.strip();
|
||||
if (clean.isEmpty()) book().remove(DataComponents.CUSTOM_NAME);
|
||||
else book().set(DataComponents.CUSTOM_NAME, Component.literal(clean));
|
||||
bookSlot.setChanged();
|
||||
broadcastChanges();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public boolean clickMenuButton(Player player, int id) {
|
||||
if (clientSide || !stillValid(player) || book().isEmpty()) return false;
|
||||
if (id == DELETE_AGE) return retireAge(player);
|
||||
int flag = switch (id) {
|
||||
case TOGGLE_DISARM -> LinkProperties.DISARM;
|
||||
case TOGGLE_MOMENTUM -> LinkProperties.MAINTAIN_MOMENTUM;
|
||||
case TOGGLE_INTRA -> LinkProperties.INTRA_LINKING;
|
||||
default -> 0;
|
||||
};
|
||||
if (flag == 0 || (flag == LinkProperties.INTRA_LINKING
|
||||
&& book().is(ModItems.DESCRIPTIVE_BOOK.get()))) return false;
|
||||
int flags = book().getOrDefault(ModDataComponents.LINK_PROPERTIES.get(), LinkProperties.NONE).flags();
|
||||
book().set(ModDataComponents.LINK_PROPERTIES.get(), new LinkProperties(flags ^ flag));
|
||||
bookSlot.setChanged();
|
||||
pendingAge = null;
|
||||
confirmation = 0;
|
||||
broadcastChanges();
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean retireAge(Player player) {
|
||||
if (!(player instanceof ServerPlayer serverPlayer) || !canDelete(player)) return false;
|
||||
AgeReference reference = book().get(ModDataComponents.AGE_REFERENCE.get());
|
||||
if (reference == null) return false;
|
||||
AgeDefinition age = AgeRegistrySavedData.get(serverPlayer.serverLevel()).find(reference.id()).orElse(null);
|
||||
if (age == null) return false;
|
||||
if (confirmation != 1 || !reference.id().equals(pendingAge)) {
|
||||
pendingAge = reference.id();
|
||||
confirmation = 1;
|
||||
broadcastChanges();
|
||||
return true;
|
||||
}
|
||||
confirmation = 0;
|
||||
pendingAge = null;
|
||||
AgeDeletionService.Result result = AgeDeletionService.retire(serverPlayer.getServer(), age);
|
||||
player.displayClientMessage(Component.translatable(
|
||||
"message.rivencraft.link_modifier.delete." + result.name().toLowerCase()), false);
|
||||
broadcastChanges();
|
||||
return result == AgeDeletionService.Result.RETIRED;
|
||||
}
|
||||
|
||||
@Override public boolean stillValid(Player player) {
|
||||
return modifier != null && modifier.stillValid(player);
|
||||
}
|
||||
|
||||
@Override public ItemStack quickMoveStack(Player player, int index) {
|
||||
Slot slot = slots.get(index);
|
||||
if (!slot.hasItem()) return ItemStack.EMPTY;
|
||||
ItemStack source = slot.getItem();
|
||||
ItemStack copy = source.copy();
|
||||
if (index == 0) {
|
||||
if (!moveItemStackTo(source, PLAYER_START, slots.size(), true)) return ItemStack.EMPTY;
|
||||
} else if (!moveItemStackTo(source, 0, 1, false)) return ItemStack.EMPTY;
|
||||
if (source.isEmpty()) slot.set(ItemStack.EMPTY);
|
||||
else slot.setChanged();
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import net.dnorberg.rivencraft.registry.ModMenus;
|
||||
import net.dnorberg.rivencraft.writing.NotebookContents;
|
||||
import net.dnorberg.rivencraft.writing.PageContent;
|
||||
import net.dnorberg.rivencraft.item.PageItem;
|
||||
import net.dnorberg.rivencraft.item.CollationFolderItem;
|
||||
import net.minecraft.core.NonNullList;
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
import net.minecraft.world.Container;
|
||||
@@ -28,6 +29,7 @@ public final class NotebookMenu extends AbstractContainerMenu {
|
||||
private final InteractionHand hand;
|
||||
private final int openedHotbarSlot;
|
||||
private final NotebookContainer contents;
|
||||
private final int pageCapacity;
|
||||
|
||||
public NotebookMenu(int containerId, Inventory inventory, RegistryFriendlyByteBuf data) {
|
||||
this(containerId, inventory, data.readBoolean() ? InteractionHand.OFF_HAND : InteractionHand.MAIN_HAND);
|
||||
@@ -38,11 +40,12 @@ public final class NotebookMenu extends AbstractContainerMenu {
|
||||
this.playerInventory = inventory;
|
||||
this.hand = hand;
|
||||
this.openedHotbarSlot = inventory.selected;
|
||||
this.pageCapacity = CollationFolderItem.capacity(inventory.player.getItemInHand(hand));
|
||||
this.contents = new NotebookContainer(inventory, hand);
|
||||
|
||||
for (int page = 0; page < NOTEBOOK_SLOTS; page++) {
|
||||
// The client draws these pages on a filtered, scrollable surface instead.
|
||||
addSlot(new PageSlot(contents, page, -1000, -1000));
|
||||
addSlot(new PageSlot(contents, page, -1000, -1000, page < pageCapacity));
|
||||
}
|
||||
for (int row = 0; row < 3; row++) {
|
||||
for (int column = 0; column < 9; column++) {
|
||||
@@ -61,11 +64,13 @@ public final class NotebookMenu extends AbstractContainerMenu {
|
||||
: new Slot(playerInventory, inventoryIndex, x, y);
|
||||
}
|
||||
|
||||
public int pageCapacity() { return pageCapacity; }
|
||||
|
||||
@Override
|
||||
public boolean stillValid(Player player) {
|
||||
return player.isAlive()
|
||||
&& (hand == InteractionHand.OFF_HAND || player.getInventory().selected == openedHotbarSlot)
|
||||
&& player.getItemInHand(hand).is(ModItems.NOTEBOOK.get());
|
||||
&& CollationFolderItem.isCollection(player.getItemInHand(hand));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -108,7 +113,7 @@ public final class NotebookMenu extends AbstractContainerMenu {
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
if (source < 0 || source >= NOTEBOOK_SLOTS || target < 0 || target >= NOTEBOOK_SLOTS
|
||||
if (source < 0 || source >= pageCapacity || target < 0 || target >= pageCapacity
|
||||
|| contents.getItem(source).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
@@ -132,14 +137,18 @@ public final class NotebookMenu extends AbstractContainerMenu {
|
||||
}
|
||||
|
||||
private static final class PageSlot extends Slot {
|
||||
private PageSlot(Container container, int slot, int x, int y) {
|
||||
private final boolean enabled;
|
||||
|
||||
private PageSlot(Container container, int slot, int x, int y, boolean enabled) {
|
||||
super(container, slot, x, y);
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
@Override public boolean mayPlace(ItemStack stack) {
|
||||
return stack.is(ModItems.PAGE.get())
|
||||
return enabled && stack.is(ModItems.PAGE.get())
|
||||
&& PageItem.content(stack).kind() != PageContent.Kind.LINK_PANEL;
|
||||
}
|
||||
@Override public boolean mayPickup(Player player) { return enabled; }
|
||||
@Override public int getMaxStackSize() { return 1; }
|
||||
}
|
||||
|
||||
@@ -184,11 +193,12 @@ public final class NotebookMenu extends AbstractContainerMenu {
|
||||
public void setChanged() {
|
||||
super.setChanged();
|
||||
ItemStack notebook = inventory.player.getItemInHand(hand);
|
||||
if (loading || !notebook.is(ModItems.NOTEBOOK.get())) {
|
||||
if (loading || !CollationFolderItem.isCollection(notebook)) {
|
||||
return;
|
||||
}
|
||||
NonNullList<ItemStack> saved = NonNullList.withSize(NOTEBOOK_SLOTS, ItemStack.EMPTY);
|
||||
for (int index = 0; index < NOTEBOOK_SLOTS; index++) {
|
||||
int capacity = CollationFolderItem.capacity(notebook);
|
||||
NonNullList<ItemStack> saved = NonNullList.withSize(capacity, ItemStack.EMPTY);
|
||||
for (int index = 0; index < capacity; index++) {
|
||||
ItemStack page = getItem(index);
|
||||
saved.set(index, page.isEmpty() ? ItemStack.EMPTY : page.copyWithCount(1));
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import net.dnorberg.rivencraft.registry.ModDataComponents;
|
||||
import net.dnorberg.rivencraft.registry.ModItems;
|
||||
import net.dnorberg.rivencraft.registry.ModMenus;
|
||||
import net.dnorberg.rivencraft.writing.NotebookContents;
|
||||
import net.dnorberg.rivencraft.item.CollationFolderItem;
|
||||
import net.dnorberg.rivencraft.writing.PageContent;
|
||||
import net.dnorberg.rivencraft.writing.SymbolCatalog;
|
||||
import net.dnorberg.rivencraft.writing.SymbolDisplayMetadata;
|
||||
@@ -107,7 +108,7 @@ public final class WritingDeskMenu extends AbstractContainerMenu {
|
||||
public int pageCapacity(int collectionIndex) {
|
||||
if (collectionIndex < 0 || collectionIndex >= WritingDeskBlockEntity.COLLECTIONS) return 0;
|
||||
ItemStack collection = desk.getItem(collectionIndex);
|
||||
return collection.is(ModItems.NOTEBOOK.get()) ? NotebookContents.MAX_PAGES
|
||||
return CollationFolderItem.isCollection(collection) ? CollationFolderItem.capacity(collection)
|
||||
: collection.is(ModItems.PAGE.get()) ? 1 : 0;
|
||||
}
|
||||
public List<SymbolDisplayMetadata> catalog() { return catalog; }
|
||||
@@ -183,7 +184,8 @@ public final class WritingDeskMenu extends AbstractContainerMenu {
|
||||
ItemStack carried = getCarried();
|
||||
if (clientSide || !storablePage(carried)) return false;
|
||||
ItemStack collection = desk.getItem(collectionIndex);
|
||||
if (collection.is(ModItems.NOTEBOOK.get())) {
|
||||
if (CollationFolderItem.isCollection(collection)) {
|
||||
if (pageIndex < 0 || pageIndex >= CollationFolderItem.capacity(collection)) return false;
|
||||
var pages = new java.util.ArrayList<>(collection.getOrDefault(
|
||||
ModDataComponents.NOTEBOOK_CONTENTS.get(), NotebookContents.EMPTY).pages());
|
||||
while (pages.size() <= pageIndex) pages.add(ItemStack.EMPTY);
|
||||
@@ -207,8 +209,10 @@ public final class WritingDeskMenu extends AbstractContainerMenu {
|
||||
if (!getCarried().isEmpty() || sourceCollection == targetCollection && sourcePage == targetPage) return false;
|
||||
ItemStack sourceBook = desk.getItem(sourceCollection);
|
||||
ItemStack targetBook = desk.getItem(targetCollection);
|
||||
boolean sourceNotebook = sourceBook.is(ModItems.NOTEBOOK.get());
|
||||
boolean targetNotebook = targetBook.is(ModItems.NOTEBOOK.get());
|
||||
boolean sourceNotebook = CollationFolderItem.isCollection(sourceBook);
|
||||
boolean targetNotebook = CollationFolderItem.isCollection(targetBook);
|
||||
if (sourceNotebook && sourcePage >= CollationFolderItem.capacity(sourceBook)
|
||||
|| targetNotebook && targetPage >= CollationFolderItem.capacity(targetBook)) return false;
|
||||
if ((!sourceNotebook && (!sourceBook.is(ModItems.PAGE.get()) || sourcePage != 0))
|
||||
|| (!targetNotebook && (!targetBook.is(ModItems.PAGE.get()) || targetPage != 0))) return false;
|
||||
var sourcePages = sourceNotebook ? new java.util.ArrayList<>(sourceBook.getOrDefault(
|
||||
@@ -247,7 +251,8 @@ public final class WritingDeskMenu extends AbstractContainerMenu {
|
||||
private boolean extractPage(int sourceCollection, int sourcePage, int targetSlot) {
|
||||
if (!getCarried().isEmpty() || targetSlot < -1 || targetSlot >= slots.size()) return false;
|
||||
ItemStack sourceBook = desk.getItem(sourceCollection);
|
||||
boolean sourceNotebook = sourceBook.is(ModItems.NOTEBOOK.get());
|
||||
boolean sourceNotebook = CollationFolderItem.isCollection(sourceBook);
|
||||
if (sourceNotebook && sourcePage >= CollationFolderItem.capacity(sourceBook)) return false;
|
||||
if (!sourceNotebook && (!sourceBook.is(ModItems.PAGE.get()) || sourcePage != 0)) return false;
|
||||
var sourcePages = sourceNotebook ? new java.util.ArrayList<>(sourceBook.getOrDefault(
|
||||
ModDataComponents.NOTEBOOK_CONTENTS.get(), NotebookContents.EMPTY).pages()) : null;
|
||||
@@ -263,12 +268,12 @@ public final class WritingDeskMenu extends AbstractContainerMenu {
|
||||
Slot target = slots.get(targetSlot);
|
||||
if (!target.isActive() || !target.mayPlace(moving)) return false;
|
||||
ItemStack current = target.getItem();
|
||||
if (targetSlot < WritingDeskBlockEntity.COLLECTIONS && current.is(ModItems.NOTEBOOK.get())) {
|
||||
if (targetSlot < WritingDeskBlockEntity.COLLECTIONS && CollationFolderItem.isCollection(current)) {
|
||||
var targetPages = targetSlot == sourceCollection ? sourcePages
|
||||
: new java.util.ArrayList<>(current.getOrDefault(
|
||||
ModDataComponents.NOTEBOOK_CONTENTS.get(), NotebookContents.EMPTY).pages());
|
||||
int position = -1;
|
||||
for (int i = 0; i < NotebookContents.MAX_PAGES; i++) {
|
||||
for (int i = 0; i < CollationFolderItem.capacity(current); i++) {
|
||||
if (i >= targetPages.size() || targetPages.get(i).isEmpty()) {
|
||||
position = i;
|
||||
break;
|
||||
@@ -342,7 +347,7 @@ public final class WritingDeskMenu extends AbstractContainerMenu {
|
||||
private boolean moveIntoDesk(ItemStack moving) {
|
||||
if (moving.is(ModItems.INK_VIAL.get())) return moveItemStackTo(moving,
|
||||
WritingDeskBlockEntity.INK, WritingDeskBlockEntity.INK + 1, false);
|
||||
if (moving.is(ModItems.NOTEBOOK.get())) return moveItemStackTo(moving, firstVisibleTab, firstVisibleTab + VISIBLE_TABS, false);
|
||||
if (CollationFolderItem.isCollection(moving)) return moveItemStackTo(moving, firstVisibleTab, firstVisibleTab + VISIBLE_TABS, false);
|
||||
if (moving.is(Items.PAPER))
|
||||
return moveItemStackTo(moving, WritingDeskBlockEntity.PAPER,
|
||||
WritingDeskBlockEntity.PAPER + 1, false);
|
||||
@@ -374,7 +379,8 @@ public final class WritingDeskMenu extends AbstractContainerMenu {
|
||||
|| pageIndex < 0 || pageIndex >= NotebookContents.MAX_PAGES) return ItemStack.EMPTY;
|
||||
ItemStack collection = desk.getItem(collectionIndex);
|
||||
if (collection.is(ModItems.PAGE.get())) return pageIndex == 0 ? collection : ItemStack.EMPTY;
|
||||
if (collection.is(ModItems.NOTEBOOK.get())) {
|
||||
if (CollationFolderItem.isCollection(collection)) {
|
||||
if (pageIndex >= CollationFolderItem.capacity(collection)) return ItemStack.EMPTY;
|
||||
List<ItemStack> pages = collection.getOrDefault(
|
||||
ModDataComponents.NOTEBOOK_CONTENTS.get(), NotebookContents.EMPTY).pages();
|
||||
return pageIndex < pages.size() ? pages.get(pageIndex) : ItemStack.EMPTY;
|
||||
|
||||
@@ -18,6 +18,7 @@ import net.dnorberg.rivencraft.event.AgeCelestialAngleSync;
|
||||
import net.dnorberg.rivencraft.item.DescriptiveBookItem;
|
||||
import net.dnorberg.rivencraft.item.LinkingBookItem;
|
||||
import net.dnorberg.rivencraft.menu.BookBinderMenu;
|
||||
import net.dnorberg.rivencraft.menu.LinkModifierMenu;
|
||||
import net.dnorberg.rivencraft.registry.ModDataComponents;
|
||||
import net.dnorberg.rivencraft.registry.ModItems;
|
||||
import net.dnorberg.rivencraft.writing.OrderedWriting;
|
||||
@@ -59,7 +60,7 @@ public final class ModNetworking {
|
||||
}
|
||||
|
||||
public static void register(RegisterPayloadHandlersEvent event) {
|
||||
PayloadRegistrar registrar = event.registrar("15");
|
||||
PayloadRegistrar registrar = event.registrar("16");
|
||||
registrar.playToServer(RequestDescriptiveBookScreenPayload.TYPE, RequestDescriptiveBookScreenPayload.STREAM_CODEC,
|
||||
ModNetworking::requestDescriptiveBookScreen);
|
||||
registrar.playToClient(OpenDescriptiveBookScreenPayload.TYPE, OpenDescriptiveBookScreenPayload.STREAM_CODEC,
|
||||
@@ -85,6 +86,8 @@ public final class ModNetworking {
|
||||
ClientWritingDeskCatalog::handle);
|
||||
registrar.playToServer(SetBookBinderTitlePayload.TYPE, SetBookBinderTitlePayload.STREAM_CODEC,
|
||||
ModNetworking::setBookBinderTitle);
|
||||
registrar.playToServer(SetLinkModifierTitlePayload.TYPE, SetLinkModifierTitlePayload.STREAM_CODEC,
|
||||
ModNetworking::setLinkModifierTitle);
|
||||
}
|
||||
|
||||
private static void setBookBinderTitle(SetBookBinderTitlePayload payload, IPayloadContext context) {
|
||||
@@ -95,6 +98,14 @@ public final class ModNetworking {
|
||||
}
|
||||
}
|
||||
|
||||
private static void setLinkModifierTitle(SetLinkModifierTitlePayload payload, IPayloadContext context) {
|
||||
if (context.player() instanceof ServerPlayer player
|
||||
&& player.containerMenu instanceof LinkModifierMenu menu
|
||||
&& menu.containerId == payload.containerId()) {
|
||||
menu.setTitle(player, payload.title());
|
||||
}
|
||||
}
|
||||
|
||||
private static void requestDescriptiveBookScreen(RequestDescriptiveBookScreenPayload payload, IPayloadContext context) {
|
||||
if (!(context.player() instanceof ServerPlayer player)) {
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package net.dnorberg.rivencraft.network;
|
||||
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
import net.minecraft.network.codec.ByteBufCodecs;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public record SetLinkModifierTitlePayload(int containerId, String title) implements CustomPacketPayload {
|
||||
public static final Type<SetLinkModifierTitlePayload> TYPE = new Type<>(
|
||||
ResourceLocation.fromNamespaceAndPath(Rivencraft.MOD_ID, "set_link_modifier_title"));
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, SetLinkModifierTitlePayload> STREAM_CODEC =
|
||||
StreamCodec.composite(ByteBufCodecs.VAR_INT, SetLinkModifierTitlePayload::containerId,
|
||||
ByteBufCodecs.STRING_UTF8, SetLinkModifierTitlePayload::title,
|
||||
SetLinkModifierTitlePayload::new);
|
||||
|
||||
@Override public Type<? extends CustomPacketPayload> type() { return TYPE; }
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import net.dnorberg.rivencraft.block.entity.BookStandBlockEntity;
|
||||
import net.dnorberg.rivencraft.block.entity.BookBinderBlockEntity;
|
||||
import net.dnorberg.rivencraft.block.entity.WritingDeskBlockEntity;
|
||||
import net.dnorberg.rivencraft.block.entity.InkMixerBlockEntity;
|
||||
import net.dnorberg.rivencraft.block.entity.LinkModifierBlockEntity;
|
||||
import net.dnorberg.rivencraft.block.entity.StarFissureBlockEntity;
|
||||
import net.dnorberg.rivencraft.block.entity.BookReceptacleBlockEntity;
|
||||
import net.dnorberg.rivencraft.block.entity.CrystalPortalBlockEntity;
|
||||
@@ -31,6 +32,9 @@ public final class ModBlockEntities {
|
||||
public static final DeferredHolder<BlockEntityType<?>, BlockEntityType<InkMixerBlockEntity>> INK_MIXER =
|
||||
BLOCK_ENTITIES.register("ink_mixer", () -> BlockEntityType.Builder
|
||||
.of(InkMixerBlockEntity::new, ModBlocks.INK_MIXER.get()).build(null));
|
||||
public static final DeferredHolder<BlockEntityType<?>, BlockEntityType<LinkModifierBlockEntity>> LINK_MODIFIER =
|
||||
BLOCK_ENTITIES.register("link_modifier", () -> BlockEntityType.Builder
|
||||
.of(LinkModifierBlockEntity::new, ModBlocks.LINK_MODIFIER.get()).build(null));
|
||||
public static final DeferredHolder<BlockEntityType<?>, BlockEntityType<StarFissureBlockEntity>> STAR_FISSURE =
|
||||
BLOCK_ENTITIES.register("star_fissure", () -> BlockEntityType.Builder
|
||||
.of(StarFissureBlockEntity::new, ModBlocks.STAR_FISSURE.get()).build(null));
|
||||
|
||||
@@ -5,6 +5,7 @@ import net.dnorberg.rivencraft.block.BookStandBlock;
|
||||
import net.dnorberg.rivencraft.block.BookBinderBlock;
|
||||
import net.dnorberg.rivencraft.block.WritingDeskBlock;
|
||||
import net.dnorberg.rivencraft.block.InkMixerBlock;
|
||||
import net.dnorberg.rivencraft.block.LinkModifierBlock;
|
||||
import net.dnorberg.rivencraft.block.BuddingCrystalBlock;
|
||||
import net.dnorberg.rivencraft.block.StarFissureBlock;
|
||||
import net.dnorberg.rivencraft.block.DecayBlock;
|
||||
@@ -40,6 +41,9 @@ public final class ModBlocks {
|
||||
"ink_mixer", InkMixerBlock::new,
|
||||
BlockBehaviour.Properties.ofFullCopy(Blocks.CRAFTING_TABLE).noOcclusion()
|
||||
);
|
||||
public static final DeferredBlock<LinkModifierBlock> LINK_MODIFIER = BLOCKS.registerBlock(
|
||||
"link_modifier", LinkModifierBlock::new,
|
||||
BlockBehaviour.Properties.ofFullCopy(Blocks.CRAFTING_TABLE).noOcclusion());
|
||||
public static final DeferredBlock<Block> CRYSTAL_BLOCK = BLOCKS.registerBlock("crystal_block",
|
||||
Block::new, BlockBehaviour.Properties.ofFullCopy(Blocks.AMETHYST_BLOCK).mapColor(MapColor.COLOR_BLUE));
|
||||
public static final DeferredBlock<BookReceptacleBlock> BOOK_RECEPTACLE = BLOCKS.registerBlock("book_receptacle",
|
||||
|
||||
@@ -27,9 +27,12 @@ public final class ModCreativeTabs {
|
||||
output.accept(ModItems.DESCRIPTIVE_BOOK.get());
|
||||
output.accept(ModItems.LINKING_BOOK.get());
|
||||
output.accept(ModItems.NOTEBOOK.get());
|
||||
output.accept(ModItems.COLLATION_FOLDER.get());
|
||||
output.accept(ModItems.SEALED_NOTEBOOK.get());
|
||||
output.accept(ModItems.INK_VIAL.get());
|
||||
output.accept(ModItems.WRITING_DESK.get());
|
||||
output.accept(ModItems.INK_MIXER.get());
|
||||
output.accept(ModItems.LINK_MODIFIER.get());
|
||||
output.accept(ModItems.BOOK_BINDER.get());
|
||||
output.accept(ModItems.CRYSTAL_BLOCK.get());
|
||||
output.accept(ModItems.BOOK_RECEPTACLE.get());
|
||||
|
||||
@@ -5,6 +5,8 @@ import net.dnorberg.rivencraft.item.DescriptiveBookItem;
|
||||
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.SealedNotebookItem;
|
||||
import net.dnorberg.rivencraft.item.InkVialItem;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.BlockItem;
|
||||
@@ -35,6 +37,10 @@ public final class ModItems {
|
||||
NotebookItem::new,
|
||||
new Item.Properties().stacksTo(1)
|
||||
);
|
||||
public static final DeferredItem<CollationFolderItem> COLLATION_FOLDER = ITEMS.registerItem(
|
||||
"collation_folder", CollationFolderItem::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(
|
||||
"ink_vial",
|
||||
InkVialItem::new,
|
||||
@@ -43,6 +49,7 @@ public final class ModItems {
|
||||
public static final DeferredItem<BlockItem> WRITING_DESK = ITEMS.registerSimpleBlockItem(ModBlocks.WRITING_DESK);
|
||||
public static final DeferredItem<BlockItem> BOOK_BINDER = ITEMS.registerSimpleBlockItem(ModBlocks.BOOK_BINDER);
|
||||
public static final DeferredItem<BlockItem> INK_MIXER = ITEMS.registerSimpleBlockItem(ModBlocks.INK_MIXER);
|
||||
public static final DeferredItem<BlockItem> LINK_MODIFIER = ITEMS.registerSimpleBlockItem(ModBlocks.LINK_MODIFIER);
|
||||
public static final DeferredItem<BlockItem> CRYSTAL_BLOCK = ITEMS.registerSimpleBlockItem(ModBlocks.CRYSTAL_BLOCK);
|
||||
public static final DeferredItem<BlockItem> BOOK_RECEPTACLE = ITEMS.registerSimpleBlockItem(ModBlocks.BOOK_RECEPTACLE);
|
||||
public static final DeferredItem<BlockItem> BUDDING_CRYSTAL = ITEMS.registerSimpleBlockItem(ModBlocks.BUDDING_CRYSTAL);
|
||||
|
||||
@@ -5,6 +5,7 @@ import net.dnorberg.rivencraft.menu.NotebookMenu;
|
||||
import net.dnorberg.rivencraft.menu.WritingDeskMenu;
|
||||
import net.dnorberg.rivencraft.menu.BookBinderMenu;
|
||||
import net.dnorberg.rivencraft.menu.InkMixerMenu;
|
||||
import net.dnorberg.rivencraft.menu.LinkModifierMenu;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.world.inventory.MenuType;
|
||||
import net.neoforged.bus.api.IEventBus;
|
||||
@@ -24,6 +25,8 @@ public final class ModMenus {
|
||||
MENUS.register("book_binder", () -> IMenuTypeExtension.create(BookBinderMenu::new));
|
||||
public static final DeferredHolder<MenuType<?>, MenuType<InkMixerMenu>> INK_MIXER =
|
||||
MENUS.register("ink_mixer", () -> IMenuTypeExtension.create(InkMixerMenu::new));
|
||||
public static final DeferredHolder<MenuType<?>, MenuType<LinkModifierMenu>> LINK_MODIFIER =
|
||||
MENUS.register("link_modifier", () -> IMenuTypeExtension.create(LinkModifierMenu::new));
|
||||
|
||||
private ModMenus() {
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import net.dnorberg.rivencraft.block.entity.WritingDeskBlockEntity;
|
||||
import net.dnorberg.rivencraft.item.PageItem;
|
||||
import net.dnorberg.rivencraft.registry.ModDataComponents;
|
||||
import net.dnorberg.rivencraft.registry.ModItems;
|
||||
import net.dnorberg.rivencraft.item.CollationFolderItem;
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
@@ -24,7 +25,7 @@ public final class WritingDeskCatalog {
|
||||
for (int slot = 0; slot < WritingDeskBlockEntity.COLLECTIONS; slot++) {
|
||||
ItemStack collection = desk.getItem(slot);
|
||||
if (collection.is(ModItems.PAGE.get())) add(unique, collection);
|
||||
if (collection.is(ModItems.NOTEBOOK.get())) {
|
||||
if (CollationFolderItem.isCollection(collection)) {
|
||||
List<ItemStack> pages = collection.getOrDefault(
|
||||
ModDataComponents.NOTEBOOK_CONTENTS.get(), NotebookContents.EMPTY).pages();
|
||||
pages.forEach(page -> add(unique, page));
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"variants": {
|
||||
"facing=north": { "model": "rivencraft:block/link_modifier" },
|
||||
"facing=east": { "model": "rivencraft:block/link_modifier", "y": 90 },
|
||||
"facing=south": { "model": "rivencraft:block/link_modifier", "y": 180 },
|
||||
"facing=west": { "model": "rivencraft:block/link_modifier", "y": 270 }
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,18 @@
|
||||
"item.rivencraft.link_property.intra_linking": "Intra Linking",
|
||||
"item.rivencraft.page.unresolved": "Unresolved Symbol Page",
|
||||
"item.rivencraft.notebook": "Notebook",
|
||||
"item.rivencraft.collation_folder": "Collation Folder",
|
||||
"item.rivencraft.sealed_notebook": "Sealed Notebook",
|
||||
"block.rivencraft.link_modifier": "Link Modifier",
|
||||
"container.rivencraft.link_modifier": "Link Modifier",
|
||||
"screen.rivencraft.link_modifier.title": "Book title",
|
||||
"screen.rivencraft.link_modifier.retire": "Retire Age",
|
||||
"screen.rivencraft.link_modifier.confirm": "Confirm!",
|
||||
"message.rivencraft.link_modifier.delete.retired": "Age retired. Its terrain was archived outside the active world.",
|
||||
"message.rivencraft.link_modifier.delete.already_dead": "This Age is already retired.",
|
||||
"message.rivencraft.link_modifier.delete.occupied": "Cannot retire an Age while players are inside it.",
|
||||
"message.rivencraft.link_modifier.delete.unload_failed": "Could not unload this Age; its data is unchanged.",
|
||||
"message.rivencraft.link_modifier.delete.storage_failed": "Could not archive this Age's data; it was not retired.",
|
||||
"item.rivencraft.notebook.pages": "Pages: %s/%s",
|
||||
"item.rivencraft.notebook.unresolved": "Unresolved pages: %s",
|
||||
"item.rivencraft.ink_vial": "Ink Vial",
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
{
|
||||
"parent": "block/block",
|
||||
"textures": {
|
||||
"particle": "rivencraft:block/link_modifier_base",
|
||||
"0": "rivencraft:block/link_modifier_base",
|
||||
"1": "rivencraft:block/link_modifier_details"
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"from": [ 0.0, 0.0, 0.0 ],
|
||||
"to": [ 16.0, 12.0, 16.0 ],
|
||||
"faces": {
|
||||
"north": { "texture": "#0", "uv": [ 0.0, 4.0, 4.0, 7.0 ] },
|
||||
"east": { "texture": "#0", "uv": [ 4.0, 4.0, 8.0, 7.0 ] },
|
||||
"south": { "texture": "#0", "uv": [ 8.0, 4.0, 12.0, 7.0 ] },
|
||||
"west": { "texture": "#0", "uv": [ 12.0, 4.0, 16.0, 7.0 ] },
|
||||
"up": { "texture": "#0", "uv": [ 4.0, 0.0, 8.0, 4.0 ] },
|
||||
"down": { "texture": "#0", "uv": [ 8.0, 0.0, 12.0, 4.0 ] }
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [ 1.5, 12.0, 11.0 ],
|
||||
"to": [ 3.5, 13.0, 13.0 ],
|
||||
"faces": {
|
||||
"north": { "texture": "#1", "uv": [ 0.0, 15.0, 1.0, 15.5 ] },
|
||||
"east": { "texture": "#1", "uv": [ 0.0, 14.0, 1.0, 14.5 ] },
|
||||
"south": { "texture": "#1", "uv": [ 0.0, 13.0, 1.0, 13.5 ] },
|
||||
"west": { "texture": "#1", "uv": [ 1.0, 13.0, 1.0, 13.5 ] },
|
||||
"up": { "texture": "#1", "uv": [ 2.0, 14.0, 3.0, 15.0 ] },
|
||||
"down": { "texture": "#1", "uv": [ 1.0, 13.0, 2.0, 14.0 ] }
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [ 3.7, 12.0, 11.0 ],
|
||||
"to": [ 5.7, 13.0, 13.0 ],
|
||||
"faces": {
|
||||
"north": { "texture": "#1", "uv": [ 0.0, 15.0, 1.0, 15.5 ] },
|
||||
"east": { "texture": "#1", "uv": [ 0.0, 14.0, 1.0, 14.5 ] },
|
||||
"south": { "texture": "#1", "uv": [ 0.0, 13.0, 1.0, 13.5 ] },
|
||||
"west": { "texture": "#1", "uv": [ 1.0, 13.0, 1.0, 13.5 ] },
|
||||
"up": { "texture": "#1", "uv": [ 2.0, 14.0, 3.0, 15.0 ] },
|
||||
"down": { "texture": "#1", "uv": [ 1.0, 13.0, 2.0, 14.0 ] }
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [ 5.9, 12.0, 11.0 ],
|
||||
"to": [ 7.9, 13.0, 13.0 ],
|
||||
"faces": {
|
||||
"north": { "texture": "#1", "uv": [ 0.0, 15.0, 1.0, 15.5 ] },
|
||||
"east": { "texture": "#1", "uv": [ 0.0, 14.0, 1.0, 14.5 ] },
|
||||
"south": { "texture": "#1", "uv": [ 0.0, 13.0, 1.0, 13.5 ] },
|
||||
"west": { "texture": "#1", "uv": [ 1.0, 13.0, 1.0, 13.5 ] },
|
||||
"up": { "texture": "#1", "uv": [ 2.0, 14.0, 3.0, 15.0 ] },
|
||||
"down": { "texture": "#1", "uv": [ 1.0, 13.0, 2.0, 14.0 ] }
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [ 8.1, 12.0, 11.0 ],
|
||||
"to": [ 10.1, 13.0, 13.0 ],
|
||||
"faces": {
|
||||
"north": { "texture": "#1", "uv": [ 0.0, 15.0, 1.0, 15.5 ] },
|
||||
"east": { "texture": "#1", "uv": [ 0.0, 14.0, 1.0, 14.5 ] },
|
||||
"south": { "texture": "#1", "uv": [ 0.0, 13.0, 1.0, 13.5 ] },
|
||||
"west": { "texture": "#1", "uv": [ 1.0, 13.0, 1.0, 13.5 ] },
|
||||
"up": { "texture": "#1", "uv": [ 2.0, 14.0, 3.0, 15.0 ] },
|
||||
"down": { "texture": "#1", "uv": [ 1.0, 13.0, 2.0, 14.0 ] }
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [ 10.3, 12.0, 11.0 ],
|
||||
"to": [ 12.3, 13.0, 13.0 ],
|
||||
"faces": {
|
||||
"north": { "texture": "#1", "uv": [ 0.0, 15.0, 1.0, 15.5 ] },
|
||||
"east": { "texture": "#1", "uv": [ 0.0, 14.0, 1.0, 14.5 ] },
|
||||
"south": { "texture": "#1", "uv": [ 0.0, 13.0, 1.0, 13.5 ] },
|
||||
"west": { "texture": "#1", "uv": [ 1.0, 13.0, 1.0, 13.5 ] },
|
||||
"up": { "texture": "#1", "uv": [ 2.0, 14.0, 3.0, 15.0 ] },
|
||||
"down": { "texture": "#1", "uv": [ 1.0, 13.0, 2.0, 14.0 ] }
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [ 12.5, 12.0, 11.0 ],
|
||||
"to": [ 14.5, 13.0, 13.0 ],
|
||||
"faces": {
|
||||
"north": { "texture": "#1", "uv": [ 0.0, 15.0, 1.0, 15.5 ] },
|
||||
"east": { "texture": "#1", "uv": [ 0.0, 14.0, 1.0, 14.5 ] },
|
||||
"south": { "texture": "#1", "uv": [ 0.0, 13.0, 1.0, 13.5 ] },
|
||||
"west": { "texture": "#1", "uv": [ 1.0, 13.0, 1.0, 13.5 ] },
|
||||
"up": { "texture": "#1", "uv": [ 2.0, 14.0, 3.0, 15.0 ] },
|
||||
"down": { "texture": "#1", "uv": [ 1.0, 13.0, 2.0, 14.0 ] }
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [ 2.0, 9.4, 1.0 ],
|
||||
"to": [ 14.0, 12.4, 8.0 ],
|
||||
"rotation": { "origin": [ 8.0, 8.0, 8.0 ], "axis": "x", "angle": 22.5 },
|
||||
"faces": {
|
||||
"north": { "texture": "#1", "uv": [ 0.0, 4.0, 6.0, 5.5 ] },
|
||||
"east": { "texture": "#1", "uv": [ 6.0, 4.0, 9.5, 5.5 ] },
|
||||
"south": { "texture": "#1", "uv": [ 0.0, 6.0, 6.0, 7.5 ] },
|
||||
"west": { "texture": "#1", "uv": [ 6.0, 6.0, 9.5, 7.5 ] },
|
||||
"up": { "texture": "#1", "uv": [ 0.0, 0.0, 6.0, 3.5 ] },
|
||||
"down": { "texture": "#1", "uv": [ 6.0, 0.0, 12.0, 3.5 ] }
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [ 4.0, 11.5, 3.5 ],
|
||||
"to": [ 12.0, 15.5, 5.5 ],
|
||||
"rotation": { "origin": [ 8.0, 8.0, 8.0 ], "axis": "x", "angle": 22.5 },
|
||||
"faces": {
|
||||
"north": { "texture": "#1", "uv": [ 1.0, 9.0, 5.0, 11.0 ] },
|
||||
"east": { "texture": "#1", "uv": [ 0.0, 8.0, 1.0, 11.0 ] },
|
||||
"south": { "texture": "#1", "uv": [ 6.0, 9.0, 10.0, 11.0 ] },
|
||||
"west": { "texture": "#1", "uv": [ 5.0, 9.0, 6.0, 11.0 ] },
|
||||
"up": { "texture": "#1", "uv": [ 1.0, 8.0, 5.0, 9.0 ] },
|
||||
"down": { "texture": "#1", "uv": [ 5.0, 8.0, 9.0, 9.0 ] }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"parent": "minecraft:item/generated",
|
||||
"textures": { "layer0": "rivencraft:item/collation_folder" }
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{ "parent": "rivencraft:block/link_modifier" }
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"parent": "minecraft:item/generated",
|
||||
"textures": { "layer0": "rivencraft:item/sealed_notebook" }
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 303 B |
Binary file not shown.
|
After Width: | Height: | Size: 730 B |
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "minecraft:block",
|
||||
"pools": [{ "rolls": 1, "entries": [{ "type": "minecraft:item", "name": "rivencraft:link_modifier" }] }]
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
{ "type": "minecraft:item", "name": "minecraft:leather", "weight": 5,
|
||||
"functions": [{ "function": "minecraft:set_count", "count": { "type": "minecraft:uniform", "min": 1, "max": 3 } }] },
|
||||
{ "type": "minecraft:item", "name": "rivencraft:ink_vial", "weight": 4 },
|
||||
{ "type": "minecraft:item", "name": "minecraft:book", "weight": 3 },
|
||||
{ "type": "minecraft:item", "name": "rivencraft:sealed_notebook", "weight": 20 },
|
||||
{ "type": "minecraft:item", "name": "minecraft:experience_bottle", "weight": 1 }
|
||||
]
|
||||
},
|
||||
@@ -17,7 +17,7 @@
|
||||
"rolls": { "type": "minecraft:uniform", "min": 1, "max": 2 },
|
||||
"entries": [
|
||||
{ "type": "minecraft:item", "name": "rivencraft:ink_vial", "weight": 3 },
|
||||
{ "type": "minecraft:item", "name": "minecraft:book", "weight": 2 },
|
||||
{ "type": "minecraft:item", "name": "rivencraft:sealed_notebook", "weight": 2 },
|
||||
{ "type": "minecraft:item", "name": "minecraft:experience_bottle", "weight": 1 }
|
||||
]
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user