Public Access
This commit is contained in:
1 parent
a7b2c24915
commit
84a1f32732
13 files changed
+173
-71
No files matched your search
@@ -1,11 +1,14 @@
|
||||
package net.dnorberg.rivencraft.book;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import net.dnorberg.rivencraft.item.PageItem;
|
||||
import net.dnorberg.rivencraft.registry.ModCreativeTabs;
|
||||
import net.dnorberg.rivencraft.writing.OrderedWriting;
|
||||
import net.dnorberg.rivencraft.writing.PageContent;
|
||||
import net.dnorberg.rivencraft.writing.SymbolCatalog;
|
||||
import net.dnorberg.rivencraft.writing.SymbolReference;
|
||||
import net.dnorberg.rivencraft.writing.WritingEntry;
|
||||
@@ -22,6 +25,42 @@ import net.neoforged.neoforge.gametest.PrefixGameTestTemplate;
|
||||
public final class EnvironmentWritingGameTests {
|
||||
private EnvironmentWritingGameTests() {}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void symbolCategoriesPartitionCreativePages(GameTestHelper helper) {
|
||||
var registries = helper.getLevel().registryAccess();
|
||||
var allPages = PageItem.creativeSymbolPages(registries);
|
||||
Set<ResourceLocation> collected = new HashSet<>();
|
||||
helper.assertTrue(ModCreativeTabs.SYMBOL_PAGES.keySet().containsAll(PageItem.bundledCreativeCategories()),
|
||||
"Every bundled page category must receive a creative tab");
|
||||
for (ResourceLocation category : PageItem.bundledCreativeCategories()) {
|
||||
var categoryPages = PageItem.creativeSymbolPages(registries, category);
|
||||
helper.assertFalse(categoryPages.isEmpty(), "Every category tab must contain pages: " + category);
|
||||
for (var page : categoryPages) {
|
||||
helper.assertValueEqual(PageItem.creativeCategory(page), category,
|
||||
"Category tabs must not mix symbol pages");
|
||||
helper.assertTrue(collected.add(PageItem.content(page).symbol().orElseThrow().id()),
|
||||
"A symbol page must appear in exactly one category tab");
|
||||
}
|
||||
}
|
||||
helper.assertValueEqual(collected.size(), allPages.size(),
|
||||
"Category tabs must contain every bundled and generated biome page");
|
||||
var biomePages = PageItem.creativeSymbolPages(registries,
|
||||
ResourceLocation.fromNamespaceAndPath("rivencraft", "biome"));
|
||||
var generationPages = PageItem.creativeSymbolPages(registries,
|
||||
ResourceLocation.fromNamespaceAndPath("rivencraft", "biome_generation"));
|
||||
helper.assertTrue(biomePages.stream().anyMatch(page -> PageItem.content(page).symbol().orElseThrow()
|
||||
.id().getPath().equals("biome/desert")), "Biome pages must remain in the Biome tab");
|
||||
helper.assertFalse(biomePages.stream().anyMatch(page -> PageItem.content(page).symbol().orElseThrow()
|
||||
.id().getPath().equals("biome/tiny")), "Biome generation types must leave the Biome tab");
|
||||
helper.assertTrue(generationPages.stream().anyMatch(page -> PageItem.content(page).symbol().orElseThrow()
|
||||
.id().getPath().equals("biome/tiny")), "Biome generation types need their own tab");
|
||||
helper.assertTrue(allPages.stream().noneMatch(page -> PageItem.content(page).kind() == PageContent.Kind.BLANK),
|
||||
"Blank pages must not enter the creative or JEI symbol-page feed");
|
||||
helper.assertValueEqual(PageItem.content(PageItem.createLinkPanelPage()).kind(), PageContent.Kind.LINK_PANEL,
|
||||
"Link pages must stay separate from symbol categories");
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void environmentPagesEnterCreativeAndJeiFeed(GameTestHelper helper) {
|
||||
Set<String> expected = Set.of(
|
||||
|
||||
@@ -206,18 +206,14 @@ public final class TerrainWritingGameTests {
|
||||
helper.assertTrue(level.getChunkSource().getGenerator().getBiomeSource().possibleBiomes().stream()
|
||||
.allMatch(biome -> biome.is(Biomes.THE_END)),
|
||||
"The runtime sky-island generator must use the End biome page");
|
||||
helper.assertValueEqual(AgeDimensionService.unload(server, age), AgeDimensionService.UnloadResult.UNLOADED,
|
||||
"The island Age must unload");
|
||||
var reloaded = AgeDimensionService.ensureLoaded(server, age);
|
||||
helper.assertTrue(reloaded != null && reloaded.getChunkSource().getGenerator().getBiomeSource()
|
||||
.possibleBiomes().stream().allMatch(biome -> biome.is(Biomes.THE_END)),
|
||||
"The authored End biome must survive runtime dimension reload");
|
||||
helper.assertValueEqual(AgeGenerationPlan.load(age.plan().save()).biomeSelection(),
|
||||
age.plan().biomeSelection(), "The authored End biome must survive a plan save/load round trip");
|
||||
helper.succeed();
|
||||
});
|
||||
}
|
||||
|
||||
@GameTest(template = "empty", timeoutTicks = 600)
|
||||
public static void writtenTerrainFamiliesGenerateSafelyAndReload(GameTestHelper helper) {
|
||||
public static void writtenTerrainFamiliesGenerateSafely(GameTestHelper helper) {
|
||||
var player = helper.makeMockServerPlayerInLevel();
|
||||
checkWrittenTerrain(helper, player, AgeGenerationProfiles.all(), 0);
|
||||
}
|
||||
@@ -259,14 +255,8 @@ public final class TerrainWritingGameTests {
|
||||
&& level.getBlockState(spawn.above()).isAir(),
|
||||
"Every written terrain family must retain a safe arrival platform");
|
||||
|
||||
helper.assertValueEqual(AgeDimensionService.unload(server, age),
|
||||
AgeDimensionService.UnloadResult.UNLOADED,
|
||||
"A written Age must unload without losing its plan");
|
||||
var reloaded = AgeDimensionService.ensureLoaded(server, age);
|
||||
helper.assertTrue(reloaded != null, "The written Age must reload from its frozen plan");
|
||||
assertGenerator(helper, reloaded, age.plan().runtimeProfile());
|
||||
helper.assertValueEqual(reloaded.dimensionType().effectsLocation(),
|
||||
level.dimensionType().effectsLocation(), "Reload must retain dimension effects");
|
||||
helper.assertValueEqual(AgeGenerationPlan.load(age.plan().save()).runtimeProfile(),
|
||||
age.plan().runtimeProfile(), "The written terrain profile must survive plan persistence");
|
||||
helper.runAfterDelay(1, () -> checkWrittenTerrain(helper, player, profiles, index + 1));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import mezz.jei.api.registration.IRecipeRegistration;
|
||||
import mezz.jei.api.registration.ISubtypeRegistration;
|
||||
import mezz.jei.api.registration.IExtraIngredientRegistration;
|
||||
import mezz.jei.api.ingredients.subtypes.IIngredientSubtypeInterpreter;
|
||||
import mezz.jei.api.ingredients.subtypes.UidContext;
|
||||
import mezz.jei.api.runtime.IJeiRuntime;
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.dnorberg.rivencraft.item.PageItem;
|
||||
@@ -74,6 +75,9 @@ public final class RivencraftJeiPlugin implements IModPlugin {
|
||||
|
||||
@Override
|
||||
public void onRuntimeAvailable(IJeiRuntime runtime) {
|
||||
// JEI discovers the base Page item as a blank stack; only authored pages belong in its list.
|
||||
runtime.getIngredientVisibility().hideIngredients(VanillaTypes.ITEM_STACK,
|
||||
List.of(new ItemStack(ModItems.PAGE.get())), List.of(UidContext.Ingredient));
|
||||
var connection = Minecraft.getInstance().getConnection();
|
||||
if (connection == null) return;
|
||||
Set<ResourceLocation> known = runtime.getIngredientManager().getAllItemStacks().stream()
|
||||
|
||||
@@ -313,42 +313,6 @@ public final class LinkingGameTests {
|
||||
throw new IllegalStateException("Could not select profile " + profile);
|
||||
}
|
||||
|
||||
@GameTest(template = "empty", timeoutTicks = 100)
|
||||
public static void unloadedAgeReloadsWithTheSameDefinition(GameTestHelper helper) {
|
||||
UUID id = UUID.randomUUID();
|
||||
AgeDefinition definition = AgeRegistrySavedData.resolveOrCreate(
|
||||
helper.getLevel(),
|
||||
new AgeReference(id, "Lifecycle Test Age " + id)
|
||||
);
|
||||
|
||||
var server = helper.getLevel().getServer();
|
||||
var first = AgeDimensionService.ensureLoaded(server, definition);
|
||||
helper.assertTrue(first != null, "The lifecycle test Age must load");
|
||||
helper.runAfterDelay(1, () -> {
|
||||
helper.assertValueEqual(
|
||||
AgeDimensionService.unload(server, definition),
|
||||
AgeDimensionService.UnloadResult.UNLOADED,
|
||||
"A loaded Age must unload without deleting its definition"
|
||||
);
|
||||
helper.runAfterDelay(1, () -> {
|
||||
helper.assertTrue(server.getLevel(AgeDimensionService.dimensionKey(definition)) == null,
|
||||
"An unloaded Age must leave the active server level map");
|
||||
|
||||
var reloaded = AgeDimensionService.ensureLoaded(server, definition);
|
||||
helper.assertTrue(reloaded != null, "A book must be able to reload an archived Age");
|
||||
helper.runAfterDelay(1, () -> {
|
||||
var registered = server.getLevel(AgeDimensionService.dimensionKey(definition));
|
||||
helper.assertTrue(registered != null, "The reloaded Age must rejoin the active server level map");
|
||||
helper.assertValueEqual(registered.getSeed(), definition.seed(),
|
||||
"Reloading an Age must preserve its immutable seed");
|
||||
helper.assertValueEqual(registered.dimension().location(), definition.dimensionId(),
|
||||
"Reloading an Age must preserve its dimension identifier");
|
||||
helper.succeed();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@GameTest(template = "empty", timeoutTicks = 200)
|
||||
public static void legacyBaseTerrainProfilesLoadRuntimeDimensions(GameTestHelper helper) {
|
||||
for (var profile : AgeGenerationProfiles.all()) {
|
||||
|
||||
@@ -111,7 +111,7 @@ public final class WritingDataGameTests {
|
||||
for (String terrain : List.of("standard", "amplified", "cave", "island", "flat", "void")) {
|
||||
String path = "terrain/" + terrain;
|
||||
helper.assertTrue(PageItem.bundledCreativeSymbols().contains(path),
|
||||
"The Pages creative tab must include " + path);
|
||||
"The matching symbol-page creative tab must include " + path);
|
||||
ItemStack page = PageItem.createBundledCreativePage(path);
|
||||
helper.assertValueEqual(PageItem.content(page).symbol().orElseThrow().id(),
|
||||
ResourceLocation.fromNamespaceAndPath("rivencraft", path),
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.dnorberg.rivencraft.registry.ModDataComponents;
|
||||
@@ -18,18 +19,24 @@ import net.dnorberg.rivencraft.writing.SymbolDefinition;
|
||||
import net.dnorberg.rivencraft.writing.BiomeSymbolPages;
|
||||
import net.dnorberg.rivencraft.writing.SymbolDisplayMetadata;
|
||||
import net.dnorberg.rivencraft.writing.SymbolReference;
|
||||
import net.dnorberg.rivencraft.writing.SymbolSemantics;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.TooltipFlag;
|
||||
|
||||
/** One stackable Page item whose server-owned component identifies its writing state. */
|
||||
public final class PageItem extends Item {
|
||||
private static final Map<String, SymbolDisplayMetadata> CREATIVE_SYMBOLS = loadCreativeSymbols();
|
||||
private static final ResourceLocation BIOME_GENERATION_CATEGORY =
|
||||
ResourceLocation.fromNamespaceAndPath(Rivencraft.MOD_ID, "biome_generation");
|
||||
private static final Map<String, CreativeSymbol> CREATIVE_SYMBOLS = loadCreativeSymbols();
|
||||
|
||||
private record CreativeSymbol(SymbolDisplayMetadata display, ResourceLocation creativeCategory) {}
|
||||
|
||||
public PageItem(Properties properties) {
|
||||
super(properties);
|
||||
@@ -46,6 +53,21 @@ public final class PageItem extends Item {
|
||||
return List.copyOf(CREATIVE_SYMBOLS.keySet());
|
||||
}
|
||||
|
||||
/** Bundled categories become separate creative tabs without a Java page list. */
|
||||
public static List<ResourceLocation> bundledCreativeCategories() {
|
||||
return List.copyOf(CREATIVE_SYMBOLS.values().stream()
|
||||
.map(CreativeSymbol::creativeCategory)
|
||||
.collect(java.util.stream.Collectors.toCollection(LinkedHashSet::new)));
|
||||
}
|
||||
|
||||
public static ItemStack bundledCreativeCategoryIcon(ResourceLocation category) {
|
||||
return CREATIVE_SYMBOLS.entrySet().stream()
|
||||
.filter(entry -> entry.getValue().creativeCategory().equals(category))
|
||||
.findFirst()
|
||||
.map(entry -> createBundledCreativePage(entry.getKey()))
|
||||
.orElseThrow(() -> new IllegalArgumentException("No bundled pages for category " + category));
|
||||
}
|
||||
|
||||
/** Exact symbol-page variants offered in the creative tab and to optional ingredient viewers. */
|
||||
public static List<ItemStack> creativeSymbolPages(HolderLookup.Provider registries) {
|
||||
List<ItemStack> pages = new ArrayList<>();
|
||||
@@ -60,12 +82,30 @@ public final class PageItem extends Item {
|
||||
return List.copyOf(pages);
|
||||
}
|
||||
|
||||
public static List<ItemStack> creativeSymbolPages(HolderLookup.Provider registries,
|
||||
ResourceLocation category) {
|
||||
return creativeSymbolPages(registries).stream()
|
||||
.filter(page -> category.equals(creativeCategory(page)))
|
||||
.toList();
|
||||
}
|
||||
|
||||
public static ResourceLocation creativeCategory(ItemStack page) {
|
||||
SymbolDisplayMetadata metadata = page.get(ModDataComponents.PAGE_SYMBOL_DISPLAY.get());
|
||||
if (metadata == null) throw new IllegalArgumentException("Page has no symbol display metadata");
|
||||
if (metadata.id().getNamespace().equals(Rivencraft.MOD_ID)) {
|
||||
CreativeSymbol bundled = CREATIVE_SYMBOLS.get(metadata.id().getPath());
|
||||
if (bundled != null) return bundled.creativeCategory();
|
||||
}
|
||||
return metadata.category();
|
||||
}
|
||||
|
||||
/** Bundled symbols for the creative tab; the server still validates catalog membership on use. */
|
||||
public static ItemStack createBundledCreativePage(String path) {
|
||||
SymbolDisplayMetadata metadata = CREATIVE_SYMBOLS.get(path);
|
||||
if (metadata == null) {
|
||||
CreativeSymbol symbol = CREATIVE_SYMBOLS.get(path);
|
||||
if (symbol == null) {
|
||||
throw new IllegalArgumentException("Not a bundled symbol: " + path);
|
||||
}
|
||||
SymbolDisplayMetadata metadata = symbol.display();
|
||||
SymbolReference reference = new SymbolReference(metadata.id());
|
||||
ItemStack page = create(PageContent.symbol(reference));
|
||||
page.set(ModDataComponents.PAGE_SYMBOL_DISPLAY.get(), metadata);
|
||||
@@ -79,7 +119,7 @@ public final class PageItem extends Item {
|
||||
return page;
|
||||
}
|
||||
|
||||
private static Map<String, SymbolDisplayMetadata> loadCreativeSymbols() {
|
||||
private static Map<String, CreativeSymbol> loadCreativeSymbols() {
|
||||
String resource = "/assets/rivencraft/creative_symbol_pages.json";
|
||||
try (var stream = PageItem.class.getResourceAsStream(resource)) {
|
||||
if (stream == null) {
|
||||
@@ -87,13 +127,18 @@ public final class PageItem extends Item {
|
||||
}
|
||||
var json = JsonParser.parseReader(new InputStreamReader(stream, StandardCharsets.UTF_8))
|
||||
.getAsJsonObject();
|
||||
Map<String, SymbolDisplayMetadata> symbols = new LinkedHashMap<>();
|
||||
Map<String, CreativeSymbol> symbols = new LinkedHashMap<>();
|
||||
json.entrySet().forEach(entry -> {
|
||||
var id = net.minecraft.resources.ResourceLocation.parse(entry.getKey());
|
||||
if (!id.getNamespace().equals(Rivencraft.MOD_ID)) {
|
||||
throw new IllegalStateException("Creative symbol is not a Rivencraft page: " + id);
|
||||
}
|
||||
symbols.put(id.getPath(), SymbolDefinition.parse(entry.getValue()).display(id));
|
||||
SymbolDefinition definition = SymbolDefinition.parse(entry.getValue());
|
||||
ResourceLocation creativeCategory = definition.semantics()
|
||||
.filter(semantics -> semantics.role() == SymbolSemantics.Role.BIOME_CONTROLLER)
|
||||
.map(ignored -> BIOME_GENERATION_CATEGORY)
|
||||
.orElse(definition.category());
|
||||
symbols.put(id.getPath(), new CreativeSymbol(definition.display(id), creativeCategory));
|
||||
});
|
||||
return Collections.unmodifiableMap(symbols);
|
||||
} catch (java.io.IOException exception) {
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
package net.dnorberg.rivencraft.registry;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.dnorberg.rivencraft.item.PageItem;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.CreativeModeTab;
|
||||
import net.neoforged.bus.api.IEventBus;
|
||||
import net.neoforged.neoforge.registries.DeferredHolder;
|
||||
@@ -28,17 +34,39 @@ public final class ModCreativeTabs {
|
||||
})
|
||||
.build());
|
||||
|
||||
public static final DeferredHolder<CreativeModeTab, CreativeModeTab> PAGES =
|
||||
CREATIVE_TABS.register("rivencraft_pages", () -> CreativeModeTab.builder()
|
||||
.title(Component.translatable("itemGroup.rivencraft.pages"))
|
||||
public static final DeferredHolder<CreativeModeTab, CreativeModeTab> LINK_PAGES =
|
||||
CREATIVE_TABS.register("rivencraft_link_pages", () -> CreativeModeTab.builder()
|
||||
.title(Component.translatable("itemGroup.rivencraft.pages.link"))
|
||||
.icon(PageItem::createLinkPanelPage)
|
||||
.displayItems((parameters, output) -> {
|
||||
output.accept(ModItems.PAGE.get());
|
||||
output.accept(PageItem.createLinkPanelPage());
|
||||
PageItem.creativeSymbolPages(parameters.holders()).forEach(output::accept);
|
||||
})
|
||||
.displayItems((parameters, output) -> output.accept(PageItem.createLinkPanelPage()))
|
||||
.build());
|
||||
|
||||
private static final List<String> CATEGORY_ORDER = List.of(
|
||||
"terrain", "biome", "biome_generation", "weather", "lighting", "sky", "celestial", "feature");
|
||||
|
||||
public static final Map<ResourceLocation, DeferredHolder<CreativeModeTab, CreativeModeTab>> SYMBOL_PAGES =
|
||||
registerSymbolPageTabs();
|
||||
|
||||
private static Map<ResourceLocation, DeferredHolder<CreativeModeTab, CreativeModeTab>> registerSymbolPageTabs() {
|
||||
List<ResourceLocation> categories = new ArrayList<>(PageItem.bundledCreativeCategories());
|
||||
categories.sort(Comparator.comparingInt((ResourceLocation id) -> {
|
||||
int index = CATEGORY_ORDER.indexOf(id.getPath());
|
||||
return index < 0 ? CATEGORY_ORDER.size() : index;
|
||||
}).thenComparing(ResourceLocation::toString));
|
||||
Map<ResourceLocation, DeferredHolder<CreativeModeTab, CreativeModeTab>> tabs = new LinkedHashMap<>();
|
||||
for (ResourceLocation category : categories) {
|
||||
String key = "rivencraft_pages_" + category.getNamespace() + "_" + category.getPath().replace('/', '_');
|
||||
tabs.put(category, CREATIVE_TABS.register(key, () -> CreativeModeTab.builder()
|
||||
.title(Component.translatableWithFallback("itemGroup.rivencraft.pages." + category.getPath(),
|
||||
"Rivencraft " + category.getPath() + " Pages"))
|
||||
.icon(() -> PageItem.bundledCreativeCategoryIcon(category))
|
||||
.displayItems((parameters, output) -> PageItem.creativeSymbolPages(parameters.holders(), category)
|
||||
.forEach(output::accept))
|
||||
.build()));
|
||||
}
|
||||
return Map.copyOf(tabs);
|
||||
}
|
||||
|
||||
private ModCreativeTabs() {
|
||||
}
|
||||
|
||||
|
||||
@@ -230,5 +230,13 @@
|
||||
"jei.rivencraft.writing.bind": "At a Book Binder, name the Age and add a cover plus Link Panel. Optional symbol Pages follow the panel.",
|
||||
"jei.rivencraft.writing.linking_book": "Craft one Link Panel Page with leather to make an unwritten Linking Book. Open it to name and bind it.",
|
||||
"itemGroup.rivencraft": "Rivencraft",
|
||||
"itemGroup.rivencraft.pages": "Rivencraft Pages"
|
||||
"itemGroup.rivencraft.pages.link": "Rivencraft Link Pages",
|
||||
"itemGroup.rivencraft.pages.terrain": "Rivencraft Terrain Pages",
|
||||
"itemGroup.rivencraft.pages.biome": "Rivencraft Biome Pages",
|
||||
"itemGroup.rivencraft.pages.biome_generation": "Rivencraft Biome Generation Pages",
|
||||
"itemGroup.rivencraft.pages.weather": "Rivencraft Weather Pages",
|
||||
"itemGroup.rivencraft.pages.lighting": "Rivencraft Lighting Pages",
|
||||
"itemGroup.rivencraft.pages.sky": "Rivencraft Sky Pages",
|
||||
"itemGroup.rivencraft.pages.celestial": "Rivencraft Celestial Pages",
|
||||
"itemGroup.rivencraft.pages.feature": "Rivencraft Feature Pages"
|
||||
}
|
||||
Reference in new issue
Block a user