Public Access
Complete Link Modifier controls and seed-driven Age generation (#90)
Build / build (push) Canceled after 0s
Build / build (push) Canceled after 0s
This commit is contained in:
1 parent
ee905cab68
commit
09d2b0800c
26 files changed
+388
-65
No files matched your search
@@ -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);
|
||||
|
||||
@@ -96,6 +96,11 @@ 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);
|
||||
@@ -106,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()
|
||||
@@ -219,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);
|
||||
@@ -244,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,31 @@ 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();
|
||||
|
||||
@@ -16,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;
|
||||
@@ -136,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());
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -1,63 +1,121 @@
|
||||
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.dnorberg.rivencraft.registry.ModItems;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.components.EditBox;
|
||||
import net.minecraft.client.gui.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 Button disarm;
|
||||
private Button momentum;
|
||||
private Button intra;
|
||||
private Button retire;
|
||||
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 = 194;
|
||||
imageHeight = 166;
|
||||
inventoryLabelX = 8;
|
||||
inventoryLabelY = 99;
|
||||
inventoryLabelY = 72;
|
||||
}
|
||||
|
||||
@Override protected void init() {
|
||||
super.init();
|
||||
titleInput = new EditBox(font, leftPos + 8, topPos + 54, 160, 14,
|
||||
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);
|
||||
disarm = addRenderableWidget(Button.builder(Component.empty(), button -> click(LinkModifierMenu.TOGGLE_DISARM))
|
||||
.bounds(leftPos + 8, topPos + 74, 76, 20).build());
|
||||
momentum = addRenderableWidget(Button.builder(Component.empty(), button -> click(LinkModifierMenu.TOGGLE_MOMENTUM))
|
||||
.bounds(leftPos + 92, topPos + 74, 76, 20).build());
|
||||
intra = addRenderableWidget(Button.builder(Component.empty(), button -> click(LinkModifierMenu.TOGGLE_INTRA))
|
||||
.bounds(leftPos + 8, topPos + 18, 64, 20).build());
|
||||
retire = addRenderableWidget(Button.builder(Component.empty(), button -> click(LinkModifierMenu.DELETE_AGE))
|
||||
.bounds(leftPos + 104, topPos + 18, 64, 20).build());
|
||||
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) {
|
||||
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()) {
|
||||
@@ -67,39 +125,59 @@ public final class LinkModifierScreen extends AbstractContainerScreen<LinkModifi
|
||||
}
|
||||
boolean present = !book.isEmpty();
|
||||
titleInput.setEditable(present);
|
||||
LinkProperties properties = book.getOrDefault(ModDataComponents.LINK_PROPERTIES.get(), LinkProperties.NONE);
|
||||
disarm.active = present;
|
||||
disarm.setMessage(Component.literal("Disarm " + (properties.has(LinkProperties.DISARM) ? "✓" : "×")));
|
||||
momentum.active = present;
|
||||
momentum.setMessage(Component.literal("Momentum " + (properties.has(LinkProperties.MAINTAIN_MOMENTUM) ? "✓" : "×")));
|
||||
intra.visible = book.is(ModItems.LINKING_BOOK.get());
|
||||
intra.setMessage(Component.literal("Intra " + (properties.has(LinkProperties.INTRA_LINKING) ? "✓" : "×")));
|
||||
retire.visible = menu.canDelete(minecraft.player);
|
||||
retire.setMessage(Component.translatable(menu.awaitingDeletionConfirmation()
|
||||
? "screen.rivencraft.link_modifier.confirm" : "screen.rivencraft.link_modifier.retire"));
|
||||
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.fill(leftPos, topPos, leftPos + imageWidth, topPos + imageHeight, 0xFFD6C7A7);
|
||||
graphics.renderOutline(leftPos, topPos, imageWidth, imageHeight, 0xFF493C34);
|
||||
graphics.fill(leftPos + 78, topPos + 28, leftPos + 98, topPos + 48, 0xFF83785F);
|
||||
graphics.renderOutline(leftPos + 77, topPos + 27, 22, 22, 0xFF493C34);
|
||||
for (int row = 0; row < 3; row++) for (int col = 0; col < 9; col++)
|
||||
slotBackground(graphics, 8 + col * 18, 112 + row * 18);
|
||||
for (int col = 0; col < 9; col++) slotBackground(graphics, 8 + col * 18, 170);
|
||||
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 slotBackground(GuiGraphics graphics, int x, int y) {
|
||||
graphics.fill(leftPos + x - 1, topPos + y - 1, leftPos + x + 17, topPos + y + 17, 0xFF81745C);
|
||||
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, title, 8, 6, 0xFF332722, false);
|
||||
graphics.drawString(font, playerInventoryTitle, inventoryLabelX, inventoryLabelY, 0xFF332722, false);
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -29,12 +29,17 @@ public final class LinkModifierMenu extends AbstractContainerMenu {
|
||||
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;
|
||||
|
||||
@@ -48,11 +53,21 @@ public final class LinkModifierMenu extends AbstractContainerMenu {
|
||||
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; }
|
||||
});
|
||||
addSlot(new Slot(bookSlot, 0, 80, 30) {
|
||||
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() {
|
||||
@@ -62,12 +77,45 @@ public final class LinkModifierMenu extends AbstractContainerMenu {
|
||||
}
|
||||
});
|
||||
for (int row = 0; row < 3; row++) for (int col = 0; col < 9; col++)
|
||||
addSlot(new Slot(inventory, col + row * 9 + 9, 8 + col * 18, 112 + row * 18));
|
||||
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, 170));
|
||||
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())
|
||||
@@ -92,11 +140,18 @@ public final class LinkModifierMenu extends AbstractContainerMenu {
|
||||
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
|
||||
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;
|
||||
|
||||
@@ -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;
|
||||
@@ -60,7 +61,7 @@ public final class ModNetworking {
|
||||
}
|
||||
|
||||
public static void register(RegisterPayloadHandlersEvent event) {
|
||||
PayloadRegistrar registrar = event.registrar("16");
|
||||
PayloadRegistrar registrar = event.registrar("17");
|
||||
registrar.playToServer(RequestDescriptiveBookScreenPayload.TYPE, RequestDescriptiveBookScreenPayload.STREAM_CODEC,
|
||||
ModNetworking::requestDescriptiveBookScreen);
|
||||
registrar.playToClient(OpenDescriptiveBookScreenPayload.TYPE, OpenDescriptiveBookScreenPayload.STREAM_CODEC,
|
||||
@@ -88,6 +89,8 @@ public final class ModNetworking {
|
||||
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) {
|
||||
@@ -106,6 +109,13 @@ public final class ModNetworking {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -174,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;
|
||||
}
|
||||
@@ -280,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; }
|
||||
}
|
||||
@@ -15,6 +15,9 @@
|
||||
"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",
|
||||
@@ -22,6 +25,10 @@
|
||||
"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.",
|
||||
@@ -100,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",
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
@@ -4,6 +4,7 @@
|
||||
"package": "net.dnorberg.rivencraft.mixin",
|
||||
"compatibilityLevel": "JAVA_21",
|
||||
"mixins": [
|
||||
"AgeDimensionSeedMixin",
|
||||
"AgeDungeonPlacementMixin",
|
||||
"AgeStructureBiomeMixin",
|
||||
"StructurePlacementAccessor"
|
||||
|
||||
Reference in new issue
Block a user