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
|
Until generated Ages arrive in Milestone 3, written Descriptive Books resolve
|
||||||
deterministically to the Overworld, Nether, or End according to their Age UUID.
|
deterministically to the Overworld, Nether, or End according to their Age UUID.
|
||||||
The Rivencraft creative tab includes one sample book for each. Linking creates
|
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
|
Book destinations additionally require solid footing beneath the player. If
|
||||||
their recorded arrival is unsafe, they search nearby terrain for a footed spot.
|
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
|
## Requirements
|
||||||
|
|
||||||
- A Java 21 JDK
|
- 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
|
Version 0.2.6 expands destination snapshots to a 32-by-32-by-32 cube while
|
||||||
keeping the existing target-centered Ponder camera position.
|
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
|
The preview code is separated behind a renderer interface and uses an isolated
|
||||||
Ponder level populated from server-authoritative destination snapshots.
|
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.
|
# 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
|
mod_license=LGPL-3.0-or-later
|
||||||
# The mod version. See https://semver.org/
|
# 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.
|
# 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.
|
# This should match the base package used for the mod sources.
|
||||||
# See https://maven.apache.org/guides/mini/guide-naming-conventions.html
|
# 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.registry.ModBlockEntities;
|
||||||
import net.dnorberg.rivencraft.event.BookStandConversionHandler;
|
import net.dnorberg.rivencraft.event.BookStandConversionHandler;
|
||||||
import net.dnorberg.rivencraft.network.ModNetworking;
|
import net.dnorberg.rivencraft.network.ModNetworking;
|
||||||
|
import net.dnorberg.rivencraft.config.RivencraftConfig;
|
||||||
import net.neoforged.bus.api.IEventBus;
|
import net.neoforged.bus.api.IEventBus;
|
||||||
|
import net.neoforged.fml.ModContainer;
|
||||||
import net.neoforged.fml.common.Mod;
|
import net.neoforged.fml.common.Mod;
|
||||||
|
import net.neoforged.fml.config.ModConfig;
|
||||||
import net.neoforged.neoforge.common.NeoForge;
|
import net.neoforged.neoforge.common.NeoForge;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
@@ -18,7 +21,8 @@ public final class Rivencraft {
|
|||||||
public static final String MOD_ID = "rivencraft";
|
public static final String MOD_ID = "rivencraft";
|
||||||
public static final Logger LOGGER = LogUtils.getLogger();
|
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);
|
modEventBus.addListener(ModNetworking::register);
|
||||||
ModDataComponents.register(modEventBus);
|
ModDataComponents.register(modEventBus);
|
||||||
ModBlocks.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.Rivencraft;
|
||||||
import net.dnorberg.rivencraft.book.LinkTarget;
|
import net.dnorberg.rivencraft.book.LinkTarget;
|
||||||
import net.dnorberg.rivencraft.book.LinkingService;
|
import net.dnorberg.rivencraft.book.LinkingService;
|
||||||
|
import net.dnorberg.rivencraft.book.AgeVisitSavedData;
|
||||||
import net.dnorberg.rivencraft.item.DescriptiveBookItem;
|
import net.dnorberg.rivencraft.item.DescriptiveBookItem;
|
||||||
import net.dnorberg.rivencraft.registry.ModDataComponents;
|
import net.dnorberg.rivencraft.registry.ModDataComponents;
|
||||||
import net.dnorberg.rivencraft.registry.ModItems;
|
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.item.ItemStack;
|
||||||
import net.minecraft.world.level.block.Blocks;
|
import net.minecraft.world.level.block.Blocks;
|
||||||
import net.minecraft.world.phys.Vec3;
|
import net.minecraft.world.phys.Vec3;
|
||||||
|
import java.util.UUID;
|
||||||
import net.neoforged.neoforge.gametest.GameTestHolder;
|
import net.neoforged.neoforge.gametest.GameTestHolder;
|
||||||
import net.neoforged.neoforge.gametest.PrefixGameTestTemplate;
|
import net.neoforged.neoforge.gametest.PrefixGameTestTemplate;
|
||||||
|
|
||||||
@@ -63,6 +65,16 @@ public final class LinkingGameTests {
|
|||||||
helper.succeed();
|
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")
|
@GameTest(template = "empty")
|
||||||
public static void travelUsesLeafyDestinationWithoutFloor(GameTestHelper helper) {
|
public static void travelUsesLeafyDestinationWithoutFloor(GameTestHelper helper) {
|
||||||
helper.setBlock(ORIGIN.below(), Blocks.AIR);
|
helper.setBlock(ORIGIN.below(), Blocks.AIR);
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ package net.dnorberg.rivencraft.item;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import net.dnorberg.rivencraft.book.AgeReference;
|
import net.dnorberg.rivencraft.book.AgeReference;
|
||||||
|
import net.dnorberg.rivencraft.book.AgeVisitSavedData;
|
||||||
import net.dnorberg.rivencraft.book.LinkTarget;
|
import net.dnorberg.rivencraft.book.LinkTarget;
|
||||||
import net.dnorberg.rivencraft.book.LinkingService;
|
import net.dnorberg.rivencraft.book.LinkingService;
|
||||||
import net.dnorberg.rivencraft.book.VanillaAgeResolver;
|
import net.dnorberg.rivencraft.book.VanillaAgeResolver;
|
||||||
|
import net.dnorberg.rivencraft.config.RivencraftConfig;
|
||||||
import net.dnorberg.rivencraft.registry.ModDataComponents;
|
import net.dnorberg.rivencraft.registry.ModDataComponents;
|
||||||
import net.dnorberg.rivencraft.registry.ModItems;
|
import net.dnorberg.rivencraft.registry.ModItems;
|
||||||
import net.minecraft.ChatFormatting;
|
import net.minecraft.ChatFormatting;
|
||||||
@@ -55,12 +57,23 @@ public final class DescriptiveBookItem extends Item {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemStack returnBook = createReturnBook(returnTarget);
|
boolean createReturnLink = switch (RivencraftConfig.returnLinkMode()) {
|
||||||
if (!player.getInventory().add(returnBook)) {
|
case ON_CREATION -> AgeVisitSavedData.get((net.minecraft.server.level.ServerLevel) player.level())
|
||||||
player.drop(returnBook, false);
|
.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.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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,8 @@
|
|||||||
"message.rivencraft.descriptive_book.unwritten": "This Descriptive Book is unwritten",
|
"message.rivencraft.descriptive_book.unwritten": "This Descriptive Book is unwritten",
|
||||||
"message.rivencraft.descriptive_book.unavailable": "That Age is unavailable",
|
"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.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": "Book title",
|
||||||
"screen.rivencraft.book.title_hint": "Name this Linking Book",
|
"screen.rivencraft.book.title_hint": "Name this Linking Book",
|
||||||
"screen.rivencraft.book.name_prompt": "Name the book before binding it to this location",
|
"screen.rivencraft.book.name_prompt": "Name the book before binding it to this location",
|
||||||
|
|||||||
Reference in new issue
Block a user