Public Access
This commit is contained in:
1 parent
3760b0dea3
commit
b1d563fb84
9 files changed
+146
-8
No files matched your search
@@ -36,10 +36,14 @@ independently validates every link.
|
||||
Until generated Ages arrive in Milestone 3, written Descriptive Books resolve
|
||||
deterministically to the Overworld, Nether, or End according to their Age UUID.
|
||||
The Rivencraft creative tab includes one sample book for each. Linking creates
|
||||
a bound Return Link to the exact source position and orientation. Descriptive
|
||||
a bound Return Link to the exact source position and orientation on an Age's
|
||||
first successful visit. Descriptive
|
||||
Book destinations additionally require solid footing beneath the player. If
|
||||
their recorded arrival is unsafe, they search nearby terrain for a footed spot.
|
||||
|
||||
Server owners can set `descriptive_books.return_link_mode` in Rivencraft's
|
||||
server config to `ON_CREATION` (default), `ALWAYS`, or `DISABLED`.
|
||||
|
||||
## Requirements
|
||||
|
||||
- A Java 21 JDK
|
||||
|
||||
@@ -168,6 +168,12 @@ End destinations instead of their inherited Overworld biome sky tint.
|
||||
Version 0.2.6 expands destination snapshots to a 32-by-32-by-32 cube while
|
||||
keeping the existing target-centered Ponder camera position.
|
||||
|
||||
Version 0.2.7 persists each Age's first successful visit and creates its
|
||||
Return Link only for that initial arrival.
|
||||
|
||||
Version 0.2.8 makes Return Link creation a server setting: `ON_CREATION`
|
||||
(default), `ALWAYS`, or `DISABLED`.
|
||||
|
||||
The preview code is separated behind a renderer interface and uses an isolated
|
||||
Ponder level populated from server-authoritative destination snapshots.
|
||||
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ mod_name=Rivencraft
|
||||
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
|
||||
mod_license=LGPL-3.0-or-later
|
||||
# The mod version. See https://semver.org/
|
||||
mod_version=0.2.6
|
||||
mod_version=0.2.8
|
||||
# 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.
|
||||
# See https://maven.apache.org/guides/mini/guide-naming-conventions.html
|
||||
|
||||
@@ -8,8 +8,11 @@ import net.dnorberg.rivencraft.registry.ModBlocks;
|
||||
import net.dnorberg.rivencraft.registry.ModBlockEntities;
|
||||
import net.dnorberg.rivencraft.event.BookStandConversionHandler;
|
||||
import net.dnorberg.rivencraft.network.ModNetworking;
|
||||
import net.dnorberg.rivencraft.config.RivencraftConfig;
|
||||
import net.neoforged.bus.api.IEventBus;
|
||||
import net.neoforged.fml.ModContainer;
|
||||
import net.neoforged.fml.common.Mod;
|
||||
import net.neoforged.fml.config.ModConfig;
|
||||
import net.neoforged.neoforge.common.NeoForge;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
@@ -18,7 +21,8 @@ public final class Rivencraft {
|
||||
public static final String MOD_ID = "rivencraft";
|
||||
public static final Logger LOGGER = LogUtils.getLogger();
|
||||
|
||||
public Rivencraft(IEventBus modEventBus) {
|
||||
public Rivencraft(IEventBus modEventBus, ModContainer modContainer) {
|
||||
modContainer.registerConfig(ModConfig.Type.SERVER, RivencraftConfig.SPEC);
|
||||
modEventBus.addListener(ModNetworking::register);
|
||||
ModDataComponents.register(modEventBus);
|
||||
ModBlocks.register(modEventBus);
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package net.dnorberg.rivencraft.book;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.util.datafix.DataFixTypes;
|
||||
import net.minecraft.world.level.saveddata.SavedData;
|
||||
|
||||
/** World-persistent record of Ages whose initial Return Link has been created. */
|
||||
public final class AgeVisitSavedData extends SavedData {
|
||||
private static final String DATA_NAME = "rivencraft_age_visits";
|
||||
private static final String VISITED_AGES_TAG = "VisitedAges";
|
||||
private static final String AGE_ID_TAG = "AgeId";
|
||||
private static final Factory<AgeVisitSavedData> FACTORY = new Factory<>(
|
||||
AgeVisitSavedData::new,
|
||||
AgeVisitSavedData::load,
|
||||
DataFixTypes.SAVED_DATA_MAP_DATA
|
||||
);
|
||||
|
||||
private final Set<UUID> visitedAges = new HashSet<>();
|
||||
|
||||
public static AgeVisitSavedData get(ServerLevel level) {
|
||||
return level.getServer().overworld().getDataStorage().computeIfAbsent(FACTORY, DATA_NAME);
|
||||
}
|
||||
|
||||
/** Returns true exactly once for each Age UUID in a world. */
|
||||
public boolean markFirstVisit(UUID ageId) {
|
||||
if (!visitedAges.add(ageId)) {
|
||||
return false;
|
||||
}
|
||||
setDirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
private static AgeVisitSavedData load(CompoundTag tag, HolderLookup.Provider registries) {
|
||||
AgeVisitSavedData data = new AgeVisitSavedData();
|
||||
for (int index = 0; index < tag.getList(VISITED_AGES_TAG, CompoundTag.TAG_COMPOUND).size(); index++) {
|
||||
CompoundTag entry = tag.getList(VISITED_AGES_TAG, CompoundTag.TAG_COMPOUND).getCompound(index);
|
||||
if (entry.hasUUID(AGE_ID_TAG)) {
|
||||
data.visitedAges.add(entry.getUUID(AGE_ID_TAG));
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag save(CompoundTag tag, HolderLookup.Provider registries) {
|
||||
ListTag ages = new ListTag();
|
||||
for (UUID ageId : visitedAges) {
|
||||
CompoundTag entry = new CompoundTag();
|
||||
entry.putUUID(AGE_ID_TAG, ageId);
|
||||
ages.add(entry);
|
||||
}
|
||||
tag.put(VISITED_AGES_TAG, ages);
|
||||
return tag;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package net.dnorberg.rivencraft.config;
|
||||
|
||||
import net.neoforged.neoforge.common.ModConfigSpec;
|
||||
|
||||
/** Server-configurable gameplay policies. */
|
||||
public final class RivencraftConfig {
|
||||
public enum ReturnLinkMode {
|
||||
/** Create one Return Link when an Age is first reached. */
|
||||
ON_CREATION,
|
||||
/** Create a Return Link on every successful Descriptive Book arrival. */
|
||||
ALWAYS,
|
||||
/** Never create Return Links automatically. */
|
||||
DISABLED
|
||||
}
|
||||
|
||||
public static final ModConfigSpec SPEC;
|
||||
private static final ModConfigSpec.EnumValue<ReturnLinkMode> RETURN_LINK_MODE;
|
||||
|
||||
static {
|
||||
ModConfigSpec.Builder builder = new ModConfigSpec.Builder();
|
||||
builder.push("descriptive_books");
|
||||
RETURN_LINK_MODE = builder.comment(
|
||||
"Controls automatic Return Link creation for Descriptive Book travel.",
|
||||
"ON_CREATION creates one link when an Age is first reached; ALWAYS creates one on every arrival; DISABLED creates none."
|
||||
)
|
||||
.defineEnum("return_link_mode", ReturnLinkMode.ON_CREATION);
|
||||
builder.pop();
|
||||
SPEC = builder.build();
|
||||
}
|
||||
|
||||
private RivencraftConfig() {
|
||||
}
|
||||
|
||||
public static ReturnLinkMode returnLinkMode() {
|
||||
return RETURN_LINK_MODE.get();
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package net.dnorberg.rivencraft.gametest;
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.dnorberg.rivencraft.book.LinkTarget;
|
||||
import net.dnorberg.rivencraft.book.LinkingService;
|
||||
import net.dnorberg.rivencraft.book.AgeVisitSavedData;
|
||||
import net.dnorberg.rivencraft.item.DescriptiveBookItem;
|
||||
import net.dnorberg.rivencraft.registry.ModDataComponents;
|
||||
import net.dnorberg.rivencraft.registry.ModItems;
|
||||
@@ -16,6 +17,7 @@ import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import java.util.UUID;
|
||||
import net.neoforged.neoforge.gametest.GameTestHolder;
|
||||
import net.neoforged.neoforge.gametest.PrefixGameTestTemplate;
|
||||
|
||||
@@ -63,6 +65,16 @@ public final class LinkingGameTests {
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void ageVisitCreatesOnlyOneReturnLink(GameTestHelper helper) {
|
||||
AgeVisitSavedData visits = AgeVisitSavedData.get(helper.getLevel());
|
||||
UUID ageId = UUID.randomUUID();
|
||||
|
||||
helper.assertTrue(visits.markFirstVisit(ageId), "The first Age visit must create a Return Link");
|
||||
helper.assertFalse(visits.markFirstVisit(ageId), "Later visits to the same Age must not create another Return Link");
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void travelUsesLeafyDestinationWithoutFloor(GameTestHelper helper) {
|
||||
helper.setBlock(ORIGIN.below(), Blocks.AIR);
|
||||
|
||||
@@ -2,9 +2,11 @@ package net.dnorberg.rivencraft.item;
|
||||
|
||||
import java.util.List;
|
||||
import net.dnorberg.rivencraft.book.AgeReference;
|
||||
import net.dnorberg.rivencraft.book.AgeVisitSavedData;
|
||||
import net.dnorberg.rivencraft.book.LinkTarget;
|
||||
import net.dnorberg.rivencraft.book.LinkingService;
|
||||
import net.dnorberg.rivencraft.book.VanillaAgeResolver;
|
||||
import net.dnorberg.rivencraft.config.RivencraftConfig;
|
||||
import net.dnorberg.rivencraft.registry.ModDataComponents;
|
||||
import net.dnorberg.rivencraft.registry.ModItems;
|
||||
import net.minecraft.ChatFormatting;
|
||||
@@ -55,12 +57,23 @@ public final class DescriptiveBookItem extends Item {
|
||||
return false;
|
||||
}
|
||||
|
||||
ItemStack returnBook = createReturnBook(returnTarget);
|
||||
if (!player.getInventory().add(returnBook)) {
|
||||
player.drop(returnBook, false);
|
||||
boolean createReturnLink = switch (RivencraftConfig.returnLinkMode()) {
|
||||
case ON_CREATION -> AgeVisitSavedData.get((net.minecraft.server.level.ServerLevel) player.level())
|
||||
.markFirstVisit(age.id());
|
||||
case ALWAYS -> true;
|
||||
case DISABLED -> false;
|
||||
};
|
||||
if (createReturnLink) {
|
||||
ItemStack returnBook = createReturnBook(returnTarget);
|
||||
if (!player.getInventory().add(returnBook)) {
|
||||
player.drop(returnBook, false);
|
||||
}
|
||||
}
|
||||
player.level().playSound(null, player.blockPosition(), SoundEvents.PORTAL_TRAVEL, SoundSource.PLAYERS, 0.35F, 1.0F);
|
||||
player.displayClientMessage(Component.translatable("message.rivencraft.descriptive_book.linked", age.name()), true);
|
||||
player.displayClientMessage(Component.translatable(
|
||||
createReturnLink ? "message.rivencraft.descriptive_book.linked_first" : "message.rivencraft.descriptive_book.linked",
|
||||
age.name()
|
||||
), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
"message.rivencraft.descriptive_book.unwritten": "This Descriptive Book is unwritten",
|
||||
"message.rivencraft.descriptive_book.unavailable": "That Age is unavailable",
|
||||
"message.rivencraft.descriptive_book.unsafe": "No safe destination was found in that Age",
|
||||
"message.rivencraft.descriptive_book.linked": "Linked to %s — a Return Link was created",
|
||||
"message.rivencraft.descriptive_book.linked_first": "Linked to %s — a Return Link was created",
|
||||
"message.rivencraft.descriptive_book.linked": "Linked to %s",
|
||||
"screen.rivencraft.book.title": "Book title",
|
||||
"screen.rivencraft.book.title_hint": "Name this Linking Book",
|
||||
"screen.rivencraft.book.name_prompt": "Name the book before binding it to this location",
|
||||
|
||||
Reference in new issue
Block a user