Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09d2b0800c | ||
|
|
ee905cab68 | ||
|
|
5c08f391a2 |
No files matched your search
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 151 KiB |
|
After Width: | Height: | Size: 178 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 47 KiB |
@@ -31,7 +31,7 @@ release V0.6.6.0.
|
||||
| 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,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.
|
||||
@@ -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.7
|
||||
mod_version=0.6.6.11
|
||||
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.
|
||||
|
||||
@@ -97,7 +97,11 @@ public final class BookReceptacleBlock extends BaseEntityBlock {
|
||||
LinkTarget destination = stack.is(ModItems.LINKING_BOOK.get())
|
||||
? stack.get(ModDataComponents.LINK_TARGET.get())
|
||||
: player instanceof ServerPlayer serverPlayer
|
||||
? AgeDimensionService.resolve(serverPlayer, stack.get(ModDataComponents.AGE_REFERENCE.get())).orElse(null)
|
||||
? AgeDimensionService.resolve(serverPlayer, stack.get(ModDataComponents.AGE_REFERENCE.get()),
|
||||
net.dnorberg.rivencraft.writing.OrderedWriting.EMPTY,
|
||||
!stack.getOrDefault(ModDataComponents.LINK_PROPERTIES.get(),
|
||||
net.dnorberg.rivencraft.book.LinkProperties.NONE)
|
||||
.has(net.dnorberg.rivencraft.book.LinkProperties.DISABLE_PLATFORM)).orElse(null)
|
||||
: null;
|
||||
if (destination == null || server.getServer().getLevel(destination.location().dimension()) == null) {
|
||||
player.displayClientMessage(Component.translatable("message.rivencraft.descriptive_book.unavailable"), true);
|
||||
|
||||
@@ -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(),
|
||||
@@ -95,7 +96,13 @@ public final class AgeDimensionService {
|
||||
|
||||
public static Optional<LinkTarget> resolve(ServerPlayer player, AgeReference reference,
|
||||
OrderedWriting writing) {
|
||||
return resolve(player, reference, writing, true);
|
||||
}
|
||||
|
||||
public static Optional<LinkTarget> resolve(ServerPlayer player, AgeReference reference,
|
||||
OrderedWriting writing, boolean generatePlatform) {
|
||||
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();
|
||||
@@ -104,7 +111,8 @@ public final class AgeDimensionService {
|
||||
BlockPos spawn = definition.spawn();
|
||||
if (spawn == null) {
|
||||
applyInitialCelestialPhase(level, definition);
|
||||
spawn = createSafeSpawn(level, definition);
|
||||
spawn = createSafeSpawn(level, definition, generatePlatform);
|
||||
if (spawn == null) return Optional.empty();
|
||||
if (AgeFeatureSelection.resolved(definition.plan()).stream()
|
||||
.anyMatch(choice -> choice.id().equals(AgeFeatureSelection.STAR_FISSURES))
|
||||
|| definition.randomFeatures().stream()
|
||||
@@ -122,6 +130,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) {
|
||||
@@ -216,21 +225,24 @@ public final class AgeDimensionService {
|
||||
return ResourceKey.create(net.minecraft.core.registries.Registries.DIMENSION, definition.dimensionId());
|
||||
}
|
||||
|
||||
private static BlockPos createSafeSpawn(ServerLevel level, AgeDefinition age) {
|
||||
private static BlockPos createSafeSpawn(ServerLevel level, AgeDefinition age, boolean generatePlatform) {
|
||||
BlockPos floorCenter;
|
||||
if (age.generationProfile().equals(AgeGenerationProfiles.CAVE)) {
|
||||
// Cave Ages are enclosed. Searching from the world ceiling would
|
||||
// select or expose their roof, so begin with the floor beneath a
|
||||
// player standing at Y=64 and search downward from there.
|
||||
floorCenter = highestFootedSurface(level, CAVE_SPAWN_SEARCH_Y - 1)
|
||||
.orElseGet(() -> fallbackSpawnFloor(level));
|
||||
.orElseGet(() -> generatePlatform ? fallbackSpawnFloor(level) : null);
|
||||
} else if (isNetherStyle(level)) {
|
||||
floorCenter = netherSpawnFloor(level);
|
||||
floorCenter = generatePlatform ? netherSpawnFloor(level)
|
||||
: highestFootedSurface(level, CAVE_SPAWN_SEARCH_Y - 1).orElse(null);
|
||||
} else {
|
||||
floorCenter = highestFootedSurface(level).orElseGet(() -> fallbackSpawnFloor(level));
|
||||
floorCenter = highestFootedSurface(level).orElseGet(() -> generatePlatform ? fallbackSpawnFloor(level) : null);
|
||||
}
|
||||
|
||||
for (int x = -PLATFORM_RADIUS; x <= PLATFORM_RADIUS; x++) {
|
||||
if (floorCenter == null) return null;
|
||||
|
||||
if (generatePlatform) for (int x = -PLATFORM_RADIUS; x <= PLATFORM_RADIUS; x++) {
|
||||
for (int z = -PLATFORM_RADIUS; z <= PLATFORM_RADIUS; z++) {
|
||||
BlockPos floor = floorCenter.offset(x, 0, z);
|
||||
level.setBlock(floor, ModBlocks.CLOUD_PLATFORM.get().defaultBlockState(), 2);
|
||||
@@ -241,6 +253,11 @@ public final class AgeDimensionService {
|
||||
}
|
||||
|
||||
BlockPos spawn = floorCenter.above();
|
||||
if (!generatePlatform) {
|
||||
var safe = LinkingService.resolveDestination(level, spawn, true);
|
||||
if (safe.isEmpty()) return null;
|
||||
spawn = BlockPos.containing(safe.get());
|
||||
}
|
||||
level.setDefaultSpawnPos(spawn, 0.0F);
|
||||
return spawn;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,49 @@ public final class AgePersistenceGameTests {
|
||||
private AgePersistenceGameTests() {
|
||||
}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void modifierSeedChangePersistsWithoutChangingAgeIdentity(GameTestHelper helper) {
|
||||
UUID id = UUID.randomUUID();
|
||||
AgeDefinition original = AgeRegistrySavedData.resolveOrCreate(helper.getLevel(),
|
||||
new AgeReference(id, "Reseed " + id));
|
||||
AgeRegistrySavedData registry = AgeRegistrySavedData.get(helper.getLevel());
|
||||
long newSeed = original.seed() == 987654321L ? 987654322L : 987654321L;
|
||||
helper.assertTrue(registry.reseed(helper.getLevel().getServer(), id, newSeed),
|
||||
"An unoccupied Age must accept a new seed");
|
||||
AgeDefinition changed = registry.find(id).orElseThrow();
|
||||
helper.assertValueEqual(changed.id(), original.id(), "Reseeding must preserve the Age identity");
|
||||
helper.assertValueEqual(changed.dimensionId(), original.dimensionId(),
|
||||
"Reseeding must preserve its dimension and existing chunk location");
|
||||
helper.assertValueEqual(changed.seed(), newSeed, "The new generator seed must be saved");
|
||||
AgeDefinition restored = AgeRegistrySavedData.load(
|
||||
registry.save(new CompoundTag(), helper.getLevel().registryAccess()),
|
||||
helper.getLevel().registryAccess()).find(id).orElseThrow();
|
||||
helper.assertValueEqual(restored.seed(), newSeed, "The new seed must survive reload");
|
||||
var ageLevel = AgeDimensionService.ensureLoaded(helper.getLevel().getServer(), changed);
|
||||
helper.assertTrue(ageLevel != null, "The reseeded Age must reload");
|
||||
helper.assertValueEqual(ageLevel.getSeed(), newSeed,
|
||||
"The runtime level must use the saved seed for new chunks");
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
@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;
|
||||
@@ -13,6 +16,7 @@ import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.util.datafix.DataFixTypes;
|
||||
import net.minecraft.world.level.saveddata.SavedData;
|
||||
import net.neoforged.fml.ModList;
|
||||
@@ -21,6 +25,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 +55,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 +116,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) {
|
||||
@@ -121,6 +137,32 @@ public final class AgeRegistrySavedData extends SavedData {
|
||||
return updated;
|
||||
}
|
||||
|
||||
/** Changes the generator for future chunks; existing chunk data is deliberately preserved. */
|
||||
public boolean reseed(MinecraftServer server, UUID ageId, long seed) {
|
||||
AgeDefinition previous = ages.get(ageId);
|
||||
if (previous == null || isDead(ageId)) return false;
|
||||
if (previous.seed() == seed) return true;
|
||||
ServerLevel loaded = server.getLevel(AgeDimensionService.dimensionKey(previous));
|
||||
if (loaded != null && !loaded.players().isEmpty()) return false;
|
||||
|
||||
// Legacy changes the Age's seed, not its already-written pages or selected traits.
|
||||
// Keeping the frozen plan and feature list prevents a visited Age from changing
|
||||
// its identity while newly generated chunks use the revised terrain noise.
|
||||
AgeGenerationPlan oldPlan = previous.plan();
|
||||
AgeGenerationPlan plan = oldPlan == null ? null : new AgeGenerationPlan(oldPlan.version(), seed,
|
||||
oldPlan.writing(), oldPlan.symbols(), oldPlan.runtimeProfile(), oldPlan.biomeSelection(),
|
||||
oldPlan.activeSymbolIndices(), oldPlan.writingInstability());
|
||||
AgeDefinition changed = new AgeDefinition(previous.id(), previous.name(), seed,
|
||||
previous.generationProfile(), previous.generationVersion(), previous.createdGameTime(),
|
||||
previous.dimensionId(), previous.spawn(), previous.traits(), plan, previous.seasonMode(),
|
||||
previous.randomFeatures(), previous.instability(), previous.instabilityPlan());
|
||||
if (AgeDimensionService.unload(server, previous) == AgeDimensionService.UnloadResult.FAILED)
|
||||
return false;
|
||||
ages.put(ageId, changed);
|
||||
setDirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
private AgeDefinition getOrCreate(AgeReference reference, OrderedWriting writing,
|
||||
long worldSeed, long createdGameTime, RegistryAccess registries) {
|
||||
AgeDefinition existing = ages.get(reference.id());
|
||||
@@ -161,6 +203,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 +311,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,11 +11,15 @@ import net.minecraft.network.chat.Component;
|
||||
/** Flags written into a Link Panel and carried by books assembled from it. */
|
||||
public record LinkProperties(int flags) {
|
||||
public static final int DISARM = 1;
|
||||
/** Retained only to read pre-0.4.7.1 test books; no longer generated or applied. */
|
||||
/** Retained to read older test books; a newly edited book uses DISABLE_PLATFORM instead. */
|
||||
public static final int GENERATE_PLATFORM = 2;
|
||||
public static final int MAINTAIN_MOMENTUM = 4;
|
||||
public static final int INTRA_LINKING = 8;
|
||||
private static final int ALL_FLAGS = DISARM | GENERATE_PLATFORM | MAINTAIN_MOMENTUM | INTRA_LINKING;
|
||||
public static final int INTRA_LINKING_ONLY = 16;
|
||||
public static final int RELATIVE_LINK = 32;
|
||||
public static final int DISABLE_PLATFORM = 64;
|
||||
private static final int ALL_FLAGS = DISARM | GENERATE_PLATFORM | MAINTAIN_MOMENTUM | INTRA_LINKING
|
||||
| INTRA_LINKING_ONLY | RELATIVE_LINK | DISABLE_PLATFORM;
|
||||
public static final LinkProperties NONE = new LinkProperties(0);
|
||||
public static final Codec<LinkProperties> CODEC = Codec.intRange(0, ALL_FLAGS).xmap(
|
||||
LinkProperties::new, LinkProperties::flags);
|
||||
@@ -35,13 +39,16 @@ public record LinkProperties(int flags) {
|
||||
|
||||
/** Intra Linking only governs Linking Books; a Descriptive Book treats that panel as ordinary. */
|
||||
public LinkProperties forDescriptiveBook() {
|
||||
return new LinkProperties(flags & ~INTRA_LINKING);
|
||||
return new LinkProperties(flags & ~(INTRA_LINKING | INTRA_LINKING_ONLY));
|
||||
}
|
||||
|
||||
public void appendTooltip(List<Component> lines) {
|
||||
if (has(DISARM)) lines.add(propertyName("disarm"));
|
||||
if (has(MAINTAIN_MOMENTUM)) lines.add(propertyName("maintain_momentum"));
|
||||
if (has(INTRA_LINKING)) lines.add(propertyName("intra_linking"));
|
||||
if (has(INTRA_LINKING_ONLY)) lines.add(propertyName("intra_linking_only"));
|
||||
if (has(RELATIVE_LINK)) lines.add(propertyName("relative_link"));
|
||||
if (has(DISABLE_PLATFORM)) lines.add(propertyName("disable_platform"));
|
||||
}
|
||||
|
||||
private static Component propertyName(String name) {
|
||||
@@ -54,6 +61,9 @@ public record LinkProperties(int flags) {
|
||||
case DISARM -> propertyName("disarm");
|
||||
case MAINTAIN_MOMENTUM -> propertyName("maintain_momentum");
|
||||
case INTRA_LINKING -> propertyName("intra_linking");
|
||||
case INTRA_LINKING_ONLY -> propertyName("intra_linking_only");
|
||||
case RELATIVE_LINK -> propertyName("relative_link");
|
||||
case DISABLE_PLATFORM -> propertyName("disable_platform");
|
||||
default -> throw new IllegalArgumentException("Unknown link property flag: " + flag);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -86,7 +86,10 @@ public final class LinkingService {
|
||||
if (destinationLevel == null) {
|
||||
return LinkResult.MISSING_DIMENSION;
|
||||
}
|
||||
Optional<Vec3> destination = resolveDestination(destinationLevel, target.location().pos(), true);
|
||||
if (properties.has(LinkProperties.INTRA_LINKING_ONLY) && destinationLevel != player.serverLevel())
|
||||
return LinkResult.INTRA_ONLY;
|
||||
BlockPos origin = landingOrigin(player, destinationLevel, target, properties);
|
||||
Optional<Vec3> destination = resolveDestination(destinationLevel, origin, true);
|
||||
if (destination.isEmpty()) {
|
||||
return LinkResult.NO_SAFE_DESTINATION;
|
||||
}
|
||||
@@ -105,7 +108,10 @@ public final class LinkingService {
|
||||
ServerLevel destinationLevel = player.getServer().getLevel(target.location().dimension());
|
||||
if (destinationLevel == null) return LinkResult.MISSING_DIMENSION;
|
||||
boolean instant = destinationChunksLoaded(destinationLevel, target.location().pos());
|
||||
Optional<Vec3> destination = resolveDestination(destinationLevel, target.location().pos(), true);
|
||||
if (properties.has(LinkProperties.INTRA_LINKING_ONLY) && destinationLevel != player.serverLevel())
|
||||
return LinkResult.INTRA_ONLY;
|
||||
Optional<Vec3> destination = resolveDestination(destinationLevel,
|
||||
landingOrigin(player, destinationLevel, target, properties), true);
|
||||
if (destination.isEmpty()) return LinkResult.NO_SAFE_DESTINATION;
|
||||
Vec3 momentum = player.getDeltaMovement();
|
||||
if (properties.has(LinkProperties.DISARM)) dropInventoryAtSource(player);
|
||||
@@ -121,11 +127,15 @@ public final class LinkingService {
|
||||
if (destinationLevel == null) {
|
||||
return LinkResult.MISSING_DIMENSION;
|
||||
}
|
||||
if (destinationLevel == player.level() && !properties.has(LinkProperties.INTRA_LINKING))
|
||||
if (properties.has(LinkProperties.INTRA_LINKING_ONLY) && destinationLevel != player.serverLevel())
|
||||
return LinkResult.INTRA_ONLY;
|
||||
if (destinationLevel == player.level() && !properties.has(LinkProperties.INTRA_LINKING)
|
||||
&& !properties.has(LinkProperties.INTRA_LINKING_ONLY))
|
||||
return LinkResult.INTRA_LINKING_REQUIRED;
|
||||
|
||||
boolean instant = !portal || destinationChunksLoaded(destinationLevel, target.location().pos());
|
||||
Optional<Vec3> destination = resolveDestination(destinationLevel, target.location().pos(), requiresFooting);
|
||||
Optional<Vec3> destination = resolveDestination(destinationLevel,
|
||||
landingOrigin(player, destinationLevel, target, properties), requiresFooting);
|
||||
if (destination.isEmpty()) {
|
||||
return LinkResult.NO_SAFE_DESTINATION;
|
||||
}
|
||||
@@ -139,6 +149,17 @@ public final class LinkingService {
|
||||
return LinkResult.SUCCESS;
|
||||
}
|
||||
|
||||
/** Legacy relative links retain the traveller's offset from the source spawn. */
|
||||
private static BlockPos landingOrigin(ServerPlayer player, ServerLevel destinationLevel,
|
||||
LinkTarget target, LinkProperties properties) {
|
||||
if (!properties.has(LinkProperties.RELATIVE_LINK)) return target.location().pos();
|
||||
BlockPos sourceSpawn = player.serverLevel().getSharedSpawnPos();
|
||||
BlockPos destinationSpawn = destinationLevel.getSharedSpawnPos();
|
||||
BlockPos departure = player.blockPosition();
|
||||
return destinationSpawn.offset(departure.getX() - sourceSpawn.getX(),
|
||||
departure.getY() - sourceSpawn.getY(), departure.getZ() - sourceSpawn.getZ());
|
||||
}
|
||||
|
||||
/** Drop each inventory stack as an item entity at the departure point. */
|
||||
private static void dropInventoryAtSource(ServerPlayer player) {
|
||||
var inventory = player.getInventory();
|
||||
@@ -263,6 +284,7 @@ public final class LinkingService {
|
||||
SUCCESS,
|
||||
MISSING_DIMENSION,
|
||||
INTRA_LINKING_REQUIRED,
|
||||
INTRA_ONLY,
|
||||
NO_SAFE_DESTINATION
|
||||
}
|
||||
}
|
||||
@@ -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,183 @@
|
||||
package net.dnorberg.rivencraft.client.screen;
|
||||
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.dnorberg.rivencraft.book.LinkProperties;
|
||||
import net.dnorberg.rivencraft.menu.LinkModifierMenu;
|
||||
import net.dnorberg.rivencraft.network.SetLinkModifierTitlePayload;
|
||||
import net.dnorberg.rivencraft.network.SetLinkModifierSeedPayload;
|
||||
import net.dnorberg.rivencraft.registry.ModDataComponents;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.components.EditBox;
|
||||
import net.minecraft.client.gui.components.Tooltip;
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.neoforged.neoforge.network.PacketDistributor;
|
||||
|
||||
/** Legacy single-slot layout, with server-owned link options. */
|
||||
public final class LinkModifierScreen extends AbstractContainerScreen<LinkModifierMenu> {
|
||||
private static final ResourceLocation BACKGROUND = ResourceLocation.fromNamespaceAndPath(
|
||||
Rivencraft.MOD_ID, "textures/gui/link_modifier_legacy.png");
|
||||
private static final int[] FLAGS = {LinkProperties.DISARM, LinkProperties.INTRA_LINKING,
|
||||
LinkProperties.INTRA_LINKING_ONLY, LinkProperties.MAINTAIN_MOMENTUM,
|
||||
LinkProperties.RELATIVE_LINK, LinkProperties.DISABLE_PLATFORM};
|
||||
private static final int[] IDS = {LinkModifierMenu.TOGGLE_DISARM, LinkModifierMenu.TOGGLE_INTRA,
|
||||
LinkModifierMenu.TOGGLE_INTRA_ONLY, LinkModifierMenu.TOGGLE_MOMENTUM,
|
||||
LinkModifierMenu.TOGGLE_RELATIVE, LinkModifierMenu.TOGGLE_PLATFORM};
|
||||
private EditBox titleInput;
|
||||
private EditBox seedInput;
|
||||
private ItemStack lastBook = ItemStack.EMPTY;
|
||||
private long lastSeed = Long.MIN_VALUE;
|
||||
|
||||
public LinkModifierScreen(LinkModifierMenu menu, Inventory inventory, Component title) {
|
||||
super(menu, inventory, title);
|
||||
imageWidth = 176;
|
||||
imageHeight = 166;
|
||||
inventoryLabelX = 8;
|
||||
inventoryLabelY = 72;
|
||||
}
|
||||
|
||||
@Override protected void init() {
|
||||
super.init();
|
||||
titleInput = new EditBox(font, leftPos + 80, topPos + 56, 87, 14,
|
||||
Component.translatable("screen.rivencraft.link_modifier.title"));
|
||||
titleInput.setMaxLength(32);
|
||||
titleInput.setResponder(value -> PacketDistributor.sendToServer(
|
||||
new SetLinkModifierTitlePayload(menu.containerId, value)));
|
||||
addRenderableWidget(titleInput);
|
||||
seedInput = new EditBox(font, leftPos + 80, topPos + 15, 87, 14,
|
||||
Component.translatable("screen.rivencraft.link_modifier.seed"));
|
||||
seedInput.setMaxLength(21);
|
||||
seedInput.setTooltip(Tooltip.create(Component.translatable("screen.rivencraft.link_modifier.seed_warning")));
|
||||
addRenderableWidget(seedInput);
|
||||
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 submitSeed() {
|
||||
if (seedInput.visible && !seedInput.getValue().equals(Long.toString(menu.seed())))
|
||||
PacketDistributor.sendToServer(new SetLinkModifierSeedPayload(menu.containerId, seedInput.getValue()));
|
||||
}
|
||||
|
||||
@Override public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
|
||||
if (seedInput.isFocused()) {
|
||||
if (keyCode == 257 || keyCode == 335) submitSeed();
|
||||
else if (keyCode != 256) seedInput.keyPressed(keyCode, scanCode, modifiers);
|
||||
if (keyCode == 256 || keyCode == 257 || keyCode == 335) {
|
||||
setFocused(null);
|
||||
seedInput.setFocused(false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (titleInput.isFocused()) {
|
||||
if (keyCode == 256) {
|
||||
setFocused(null);
|
||||
titleInput.setFocused(false);
|
||||
} else titleInput.keyPressed(keyCode, scanCode, modifiers);
|
||||
return true;
|
||||
}
|
||||
return super.keyPressed(keyCode, scanCode, modifiers);
|
||||
}
|
||||
|
||||
@Override public boolean mouseClicked(double mouseX, double mouseY, int button) {
|
||||
if (seedInput.isFocused() && (mouseX < seedInput.getX() || mouseX >= seedInput.getX() + seedInput.getWidth()
|
||||
|| mouseY < seedInput.getY() || mouseY >= seedInput.getY() + seedInput.getHeight())) {
|
||||
submitSeed();
|
||||
setFocused(null);
|
||||
seedInput.setFocused(false);
|
||||
}
|
||||
if (button == 0) {
|
||||
for (int index = 0; index < FLAGS.length; index++) {
|
||||
if (inside(mouseX, mouseY, 5 + (index / 3) * 20, 10 + (index % 3) * 20, 18, 18)) {
|
||||
if (!menu.book().isEmpty()) click(IDS[index]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (menu.canDelete(minecraft.player)
|
||||
&& inside(mouseX, mouseY, menu.awaitingDeletionConfirmation() ? 140 : 120, 32, 18, 18)) {
|
||||
click(LinkModifierMenu.DELETE_AGE);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return super.mouseClicked(mouseX, mouseY, button);
|
||||
}
|
||||
|
||||
private boolean inside(double mouseX, double mouseY, int x, int y, int width, int height) {
|
||||
return mouseX >= leftPos + x && mouseX < leftPos + x + width
|
||||
&& mouseY >= topPos + y && mouseY < topPos + y + height;
|
||||
}
|
||||
|
||||
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);
|
||||
seedInput.visible = menu.hasSeed();
|
||||
seedInput.setEditable(menu.hasSeed());
|
||||
if (seedInput.visible && !seedInput.isFocused() && (lastSeed != menu.seed()
|
||||
|| !ItemStack.isSameItemSameComponents(book, lastBook))) {
|
||||
lastSeed = menu.seed();
|
||||
seedInput.setValue(Long.toString(lastSeed));
|
||||
}
|
||||
}
|
||||
|
||||
@Override protected void renderBg(GuiGraphics graphics, float partialTick, int mouseX, int mouseY) {
|
||||
graphics.blit(BACKGROUND, leftPos, topPos, 0, 0, imageWidth, imageHeight, 256, 256);
|
||||
LinkProperties properties = menu.book().getOrDefault(ModDataComponents.LINK_PROPERTIES.get(), LinkProperties.NONE);
|
||||
for (int index = 0; index < FLAGS.length; index++)
|
||||
square(graphics, 5 + (index / 3) * 20, 10 + (index % 3) * 20,
|
||||
properties.has(FLAGS[index]), false);
|
||||
if (menu.canDelete(minecraft.player)) {
|
||||
square(graphics, 120, 32, menu.awaitingDeletionConfirmation(), false);
|
||||
if (menu.awaitingDeletionConfirmation()) square(graphics, 140, 32, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void square(GuiGraphics graphics, int x, int y, boolean selected, boolean danger) {
|
||||
int left = leftPos + x;
|
||||
int top = topPos + y;
|
||||
graphics.fill(left, top, left + 18, top + 18, 0xFFAAAAAA);
|
||||
graphics.fill(left + 2, top + 2, left + 17, top + 17,
|
||||
danger ? 0xFFAA5555 : selected ? 0xFFBBBBBB : 0xFF777777);
|
||||
}
|
||||
|
||||
@Override protected void renderLabels(GuiGraphics graphics, int mouseX, int mouseY) {
|
||||
graphics.drawString(font, playerInventoryTitle, inventoryLabelX, inventoryLabelY, 0x404040, false);
|
||||
}
|
||||
|
||||
@Override public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) {
|
||||
super.render(graphics, mouseX, mouseY, partialTick);
|
||||
renderTooltip(graphics, mouseX, mouseY);
|
||||
if (!menu.book().isEmpty()) {
|
||||
for (int index = 0; index < FLAGS.length; index++) {
|
||||
if (inside(mouseX, mouseY, 5 + (index / 3) * 20, 10 + (index % 3) * 20, 18, 18)) {
|
||||
Component label = FLAGS[index] == LinkProperties.DISABLE_PLATFORM
|
||||
? Component.literal("Generate Platform: "
|
||||
+ (menu.book().getOrDefault(ModDataComponents.LINK_PROPERTIES.get(), LinkProperties.NONE)
|
||||
.has(LinkProperties.DISABLE_PLATFORM) ? "Off" : "On"))
|
||||
: LinkProperties.name(FLAGS[index]);
|
||||
graphics.renderTooltip(font, label, mouseX, mouseY);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (menu.canDelete(minecraft.player)
|
||||
&& inside(mouseX, mouseY, menu.awaitingDeletionConfirmation() ? 140 : 120, 32, 18, 18))
|
||||
graphics.renderTooltip(font, Component.translatable(menu.awaitingDeletionConfirmation()
|
||||
? "screen.rivencraft.link_modifier.confirm" : "screen.rivencraft.link_modifier.retire"),
|
||||
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) + ")")
|
||||
|
||||
@@ -14,6 +14,26 @@ import net.neoforged.neoforge.gametest.PrefixGameTestTemplate;
|
||||
public final class Milestone7PolishGameTests {
|
||||
private Milestone7PolishGameTests() {}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void modifierFlagsKeepLegacyIntraRules(GameTestHelper helper) {
|
||||
LinkProperties linking = new LinkProperties(LinkProperties.DISARM | LinkProperties.INTRA_LINKING
|
||||
| LinkProperties.INTRA_LINKING_ONLY | LinkProperties.RELATIVE_LINK
|
||||
| LinkProperties.DISABLE_PLATFORM);
|
||||
helper.assertTrue(linking.has(LinkProperties.INTRA_LINKING_ONLY)
|
||||
&& linking.has(LinkProperties.RELATIVE_LINK)
|
||||
&& linking.has(LinkProperties.DISABLE_PLATFORM),
|
||||
"The new link options must retain independent flag values");
|
||||
LinkProperties descriptive = linking.forDescriptiveBook();
|
||||
helper.assertTrue(!descriptive.has(LinkProperties.INTRA_LINKING)
|
||||
&& !descriptive.has(LinkProperties.INTRA_LINKING_ONLY),
|
||||
"Intra-only controls must not constrain Descriptive Books");
|
||||
helper.assertTrue(descriptive.has(LinkProperties.DISARM)
|
||||
&& descriptive.has(LinkProperties.RELATIVE_LINK)
|
||||
&& descriptive.has(LinkProperties.DISABLE_PLATFORM),
|
||||
"Shared link options must remain active on Descriptive Books");
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void linkSoundPriorityMatchesLegacy(GameTestHelper helper) {
|
||||
helper.assertTrue(LinkVisualEffects.sound(LinkProperties.NONE, false) == ModSounds.LINK.get(),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,10 @@ public final class DescriptiveBookItem extends Item {
|
||||
}
|
||||
|
||||
LinkTarget returnTarget = LinkTarget.from(player);
|
||||
LinkTarget ageTarget = AgeDimensionService.resolve(player, age).orElse(null);
|
||||
LinkProperties properties = stack.getOrDefault(ModDataComponents.LINK_PROPERTIES.get(), LinkProperties.NONE)
|
||||
.forDescriptiveBook();
|
||||
LinkTarget ageTarget = AgeDimensionService.resolve(player, age, net.dnorberg.rivencraft.writing.OrderedWriting.EMPTY,
|
||||
!properties.has(LinkProperties.DISABLE_PLATFORM)).orElse(null);
|
||||
if (ageTarget == null) {
|
||||
player.displayClientMessage(Component.translatable("message.rivencraft.descriptive_book.unavailable"), true);
|
||||
return false;
|
||||
@@ -69,8 +72,6 @@ public final class DescriptiveBookItem extends Item {
|
||||
|
||||
ServerLevel sourceLevel = player.serverLevel();
|
||||
Vec3 sourcePosition = player.position();
|
||||
LinkProperties properties = stack.getOrDefault(ModDataComponents.LINK_PROPERTIES.get(), LinkProperties.NONE)
|
||||
.forDescriptiveBook();
|
||||
LinkingService.LinkResult result = portal
|
||||
? LinkingService.travelDescriptivePortal(player, ageTarget, properties)
|
||||
: hand == null ? LinkingService.travelWithFooting(player, ageTarget, properties)
|
||||
|
||||
@@ -79,6 +79,7 @@ public final class LinkingBookItem extends Item {
|
||||
String errorKey = switch (result) {
|
||||
case MISSING_DIMENSION -> "message.rivencraft.linking_book.missing_dimension";
|
||||
case INTRA_LINKING_REQUIRED -> "message.rivencraft.linking_book.intra_required";
|
||||
case INTRA_ONLY -> "message.rivencraft.linking_book.intra_only";
|
||||
default -> "message.rivencraft.linking_book.unsafe";
|
||||
};
|
||||
player.displayClientMessage(Component.translatable(errorKey), true);
|
||||
|
||||
@@ -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,200 @@
|
||||
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;
|
||||
public static final int TOGGLE_INTRA_ONLY = 5;
|
||||
public static final int TOGGLE_RELATIVE = 6;
|
||||
public static final int TOGGLE_PLATFORM = 7;
|
||||
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 final Player owner;
|
||||
private final int[] seedWords = new int[4];
|
||||
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;
|
||||
this.owner = inventory.player;
|
||||
addDataSlot(new DataSlot() {
|
||||
@Override public int get() { return confirmation; }
|
||||
@Override public void set(int value) { confirmation = value; }
|
||||
});
|
||||
for (int index = 0; index < seedWords.length; index++) {
|
||||
final int word = index;
|
||||
addDataSlot(new DataSlot() {
|
||||
@Override public int get() {
|
||||
return clientSide ? seedWords[word] : (int) ((seed() >>> (word * 16)) & 0xffffL);
|
||||
}
|
||||
@Override public void set(int value) { seedWords[word] = value & 0xffff; }
|
||||
});
|
||||
}
|
||||
addSlot(new Slot(bookSlot, 0, 80, 35) {
|
||||
@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, 84 + row * 18));
|
||||
for (int col = 0; col < 9; col++)
|
||||
addSlot(new Slot(inventory, col, 8 + col * 18, 142));
|
||||
}
|
||||
|
||||
public ItemStack book() { return bookSlot.getItem(0); }
|
||||
public boolean hasSeed() {
|
||||
return book().is(ModItems.DESCRIPTIVE_BOOK.get())
|
||||
&& book().has(ModDataComponents.AGE_REFERENCE.get());
|
||||
}
|
||||
public long seed() {
|
||||
if (clientSide) {
|
||||
long result = 0;
|
||||
for (int index = 0; index < seedWords.length; index++)
|
||||
result |= (seedWords[index] & 0xffffL) << (index * 16);
|
||||
return result;
|
||||
}
|
||||
if (!hasSeed()) return 0;
|
||||
AgeReference reference = book().get(ModDataComponents.AGE_REFERENCE.get());
|
||||
return AgeRegistrySavedData.get(((ServerPlayer) owner).serverLevel()).find(reference.id())
|
||||
.map(AgeDefinition::seed).orElse(0L);
|
||||
}
|
||||
|
||||
public boolean setSeed(Player player, String text) {
|
||||
if (clientSide || !stillValid(player) || !hasSeed() || text == null || text.length() > 21
|
||||
|| !(player instanceof ServerPlayer serverPlayer)) return false;
|
||||
long requested;
|
||||
try { requested = Long.parseLong(text.strip()); }
|
||||
catch (NumberFormatException exception) { return false; }
|
||||
AgeReference reference = book().get(ModDataComponents.AGE_REFERENCE.get());
|
||||
if (reference == null) return false;
|
||||
boolean changed = AgeRegistrySavedData.get(serverPlayer.serverLevel())
|
||||
.reseed(serverPlayer.getServer(), reference.id(), requested);
|
||||
player.displayClientMessage(Component.translatable(changed
|
||||
? "message.rivencraft.link_modifier.seed_changed"
|
||||
: "message.rivencraft.link_modifier.seed_failed"), false);
|
||||
if (changed) broadcastChanges();
|
||||
return changed;
|
||||
}
|
||||
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;
|
||||
case TOGGLE_INTRA_ONLY -> LinkProperties.INTRA_LINKING_ONLY;
|
||||
case TOGGLE_RELATIVE -> LinkProperties.RELATIVE_LINK;
|
||||
case TOGGLE_PLATFORM -> LinkProperties.DISABLE_PLATFORM;
|
||||
default -> 0;
|
||||
};
|
||||
if (flag == 0 || ((flag == LinkProperties.INTRA_LINKING || flag == LinkProperties.INTRA_LINKING_ONLY)
|
||||
&& book().is(ModItems.DESCRIPTIVE_BOOK.get()))) return false;
|
||||
int flags = book().getOrDefault(ModDataComponents.LINK_PROPERTIES.get(), LinkProperties.NONE).flags();
|
||||
if (flag == LinkProperties.INTRA_LINKING_ONLY && (flags & LinkProperties.INTRA_LINKING_ONLY) == 0)
|
||||
flags |= LinkProperties.INTRA_LINKING;
|
||||
if (flag == LinkProperties.INTRA_LINKING && (flags & LinkProperties.INTRA_LINKING) != 0)
|
||||
flags &= ~LinkProperties.INTRA_LINKING_ONLY;
|
||||
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;
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package net.dnorberg.rivencraft.mixin;
|
||||
|
||||
import dev.galacticraft.dynamicdimensions.impl.DynamicDimensionRegistryImpl;
|
||||
import net.dnorberg.rivencraft.book.AgeRegistrySavedData;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.dimension.LevelStem;
|
||||
import net.minecraft.world.level.storage.WorldData;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyVariable;
|
||||
|
||||
/** Give Dynamic Dimensions the saved Age seed before it constructs the level and its chunk RandomState. */
|
||||
@Mixin(value = DynamicDimensionRegistryImpl.class, remap = false)
|
||||
public abstract class AgeDimensionSeedMixin {
|
||||
@ModifyVariable(method = "createDynamicLevel(Lnet/minecraft/resources/ResourceKey;Lnet/minecraft/world/level/storage/WorldData;Lnet/minecraft/world/level/dimension/LevelStem;Lnet/minecraft/server/level/ServerLevel;)Lnet/minecraft/server/level/ServerLevel;",
|
||||
at = @At("STORE"), ordinal = 0, remap = false)
|
||||
private long rivencraft$useSavedAgeSeed(long defaultSeed, ResourceKey<Level> dimension,
|
||||
WorldData worldData, LevelStem stem, ServerLevel overworld) {
|
||||
return AgeRegistrySavedData.get(overworld).findByDimension(dimension.location())
|
||||
.map(age -> age.seed()).orElse(defaultSeed);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package net.dnorberg.rivencraft.network;
|
||||
|
||||
import net.dnorberg.rivencraft.book.LinkTarget;
|
||||
import net.dnorberg.rivencraft.book.LinkProperties;
|
||||
import net.dnorberg.rivencraft.book.LinkingService;
|
||||
import net.dnorberg.rivencraft.book.AgeReference;
|
||||
import net.dnorberg.rivencraft.book.AgeRegistrySavedData;
|
||||
@@ -18,6 +19,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 +61,7 @@ public final class ModNetworking {
|
||||
}
|
||||
|
||||
public static void register(RegisterPayloadHandlersEvent event) {
|
||||
PayloadRegistrar registrar = event.registrar("15");
|
||||
PayloadRegistrar registrar = event.registrar("17");
|
||||
registrar.playToServer(RequestDescriptiveBookScreenPayload.TYPE, RequestDescriptiveBookScreenPayload.STREAM_CODEC,
|
||||
ModNetworking::requestDescriptiveBookScreen);
|
||||
registrar.playToClient(OpenDescriptiveBookScreenPayload.TYPE, OpenDescriptiveBookScreenPayload.STREAM_CODEC,
|
||||
@@ -85,6 +87,10 @@ public final class ModNetworking {
|
||||
ClientWritingDeskCatalog::handle);
|
||||
registrar.playToServer(SetBookBinderTitlePayload.TYPE, SetBookBinderTitlePayload.STREAM_CODEC,
|
||||
ModNetworking::setBookBinderTitle);
|
||||
registrar.playToServer(SetLinkModifierTitlePayload.TYPE, SetLinkModifierTitlePayload.STREAM_CODEC,
|
||||
ModNetworking::setLinkModifierTitle);
|
||||
registrar.playToServer(SetLinkModifierSeedPayload.TYPE, SetLinkModifierSeedPayload.STREAM_CODEC,
|
||||
ModNetworking::setLinkModifierSeed);
|
||||
}
|
||||
|
||||
private static void setBookBinderTitle(SetBookBinderTitlePayload payload, IPayloadContext context) {
|
||||
@@ -95,6 +101,21 @@ 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 setLinkModifierSeed(SetLinkModifierSeedPayload payload, IPayloadContext context) {
|
||||
if (context.player() instanceof ServerPlayer player
|
||||
&& player.containerMenu instanceof LinkModifierMenu menu
|
||||
&& menu.containerId == payload.containerId())
|
||||
menu.setSeed(player, payload.seed());
|
||||
}
|
||||
|
||||
private static void requestDescriptiveBookScreen(RequestDescriptiveBookScreenPayload payload, IPayloadContext context) {
|
||||
if (!(context.player() instanceof ServerPlayer player)) {
|
||||
return;
|
||||
@@ -163,7 +184,9 @@ public final class ModNetworking {
|
||||
try {
|
||||
AgeReference reference = new AgeReference(UUID.randomUUID(), title);
|
||||
if (AgeDimensionService.resolve(player, reference, access.stack()
|
||||
.getOrDefault(ModDataComponents.BOOK_WRITING.get(), OrderedWriting.EMPTY)).isEmpty()) {
|
||||
.getOrDefault(ModDataComponents.BOOK_WRITING.get(), OrderedWriting.EMPTY),
|
||||
!access.stack().getOrDefault(ModDataComponents.LINK_PROPERTIES.get(), LinkProperties.NONE)
|
||||
.has(LinkProperties.DISABLE_PLATFORM)).isEmpty()) {
|
||||
player.displayClientMessage(Component.translatable("message.rivencraft.descriptive_book.unavailable"), true);
|
||||
return;
|
||||
}
|
||||
@@ -269,7 +292,9 @@ public final class ModNetworking {
|
||||
LinkTarget target;
|
||||
if (payload.descriptiveBook() && stack.is(ModItems.DESCRIPTIVE_BOOK.get())) {
|
||||
AgeReference age = stack.get(ModDataComponents.AGE_REFERENCE.get());
|
||||
target = age == null ? null : AgeDimensionService.resolve(player, age).orElse(null);
|
||||
target = age == null ? null : AgeDimensionService.resolve(player, age, OrderedWriting.EMPTY,
|
||||
!stack.getOrDefault(ModDataComponents.LINK_PROPERTIES.get(), LinkProperties.NONE)
|
||||
.has(LinkProperties.DISABLE_PLATFORM)).orElse(null);
|
||||
} else if (!payload.descriptiveBook() && stack.is(ModItems.LINKING_BOOK.get())) {
|
||||
target = stack.get(ModDataComponents.LINK_TARGET.get());
|
||||
} else {
|
||||
|
||||
@@ -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 SetLinkModifierSeedPayload(int containerId, String seed) implements CustomPacketPayload {
|
||||
public static final Type<SetLinkModifierSeedPayload> TYPE = new Type<>(
|
||||
ResourceLocation.fromNamespaceAndPath(Rivencraft.MOD_ID, "set_link_modifier_seed"));
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, SetLinkModifierSeedPayload> STREAM_CODEC =
|
||||
StreamCodec.composite(ByteBufCodecs.VAR_INT, SetLinkModifierSeedPayload::containerId,
|
||||
ByteBufCodecs.STRING_UTF8, SetLinkModifierSeedPayload::seed,
|
||||
SetLinkModifierSeedPayload::new);
|
||||
|
||||
@Override public Type<? extends CustomPacketPayload> type() { return TYPE; }
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,27 @@
|
||||
"item.rivencraft.link_property.disarm": "Disarm",
|
||||
"item.rivencraft.link_property.maintain_momentum": "Maintain Momentum",
|
||||
"item.rivencraft.link_property.intra_linking": "Intra Linking",
|
||||
"item.rivencraft.link_property.intra_linking_only": "Intra-Linking Only",
|
||||
"item.rivencraft.link_property.relative_link": "Relative Link",
|
||||
"item.rivencraft.link_property.disable_platform": "Generate Platform: Off",
|
||||
"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.seed": "Age seed",
|
||||
"screen.rivencraft.link_modifier.seed_warning": "Changing the seed preserves existing chunks but changes newly generated terrain. Press Enter to apply. The Age must be empty.",
|
||||
"message.rivencraft.link_modifier.seed_changed": "Age seed changed. Existing chunks remain; newly generated chunks use the new seed.",
|
||||
"message.rivencraft.link_modifier.seed_failed": "Could not change seed. Use a signed whole number and ensure no players are in the Age.",
|
||||
"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",
|
||||
@@ -88,6 +107,7 @@
|
||||
"message.rivencraft.linking_book.missing_dimension": "That dimension is unavailable",
|
||||
"message.rivencraft.linking_book.unsafe": "No safe destination was found near the link",
|
||||
"message.rivencraft.linking_book.intra_required": "This book needs an Intra Linking panel for same-dimension travel",
|
||||
"message.rivencraft.linking_book.intra_only": "This book can only link within its current dimension",
|
||||
"message.rivencraft.descriptive_book.unwritten": "This Descriptive Book is unwritten",
|
||||
"message.rivencraft.descriptive_book.created": "Created Age: %s",
|
||||
"message.rivencraft.descriptive_book.unavailable": "That Age is unavailable",
|
||||
|
||||
@@ -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" }
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 303 B |
|
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 }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"package": "net.dnorberg.rivencraft.mixin",
|
||||
"compatibilityLevel": "JAVA_21",
|
||||
"mixins": [
|
||||
"AgeDimensionSeedMixin",
|
||||
"AgeDungeonPlacementMixin",
|
||||
"AgeStructureBiomeMixin",
|
||||
"StructurePlacementAccessor"
|
||||
|
||||