Datapack export

- /iris datapack export compiles one pack dimension into a worldgen datapack for Minecraft 26.3 and later
- Bukkit and mod loaders take the same version, mode, namespace, output, format, objects and replace arguments
- The default overworld mode replaces the Overworld of a new world with the default world type
- Preset and dimension modes write a selectable world preset or a separate dimension
- The export runs in the background and publishes the zip or folder atomically
- Pack compiler stages turn noise, terrain, caves, subterrain, biomes, surface, decoration, objects and structures into a version-neutral IR
- Capability lowering fits every field to 26.3 density function ranges
- Disjoint min and max nodes fold to the input that always wins
- The 26.3 writer emits density functions, noise settings, biomes, features, structures, presets and dimensions
- Every export carries the Iris logo as its pack.png
- Unused objects, templates and features are pruned from the output
- The pruner keeps pack.png without reading references from its bytes
- Imported pack datapacks are bundled into the export
- Every approximation reports an export limit diagnostic
- Generation cost is counted against a node budget and reported as a warning
- Cave biomes over the wall palette limit use the closest kept palette
- Jigsaw exports share the template encoder, block states, destinations and publisher with the worldgen export
- DataVersion carries the pack format minor and world data version
- Both platforms expose vanilla data from the server classpath
- GoldenHashEngine and the export probes share one chunk spiral scan
- Headless 26.3 probes measure export cost and compare column and chunk likeness against Iris
- Real-codec tests decode the exported resources with the 26.3 registries
- Paper registry tests run in isolated registry scopes
- Command help and messages cover the export in every language file
This commit is contained in:
Brian Neumann-Fopiano committed 2026-10-30 22:59:04 -04:00
1 parent b00e28e42e
commit 1ed1044474
385 files changed
+48102 -535

No files matched your search

@@ -25,6 +25,7 @@ import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeEntitySpawns;
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModdedLoader;
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeBiomeRegistry;
import art.arcane.iris.BuildConstants;
import art.arcane.iris.platform.ClasspathVanillaDataSource;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeCommandExecutor;
import art.arcane.iris.spi.PlatformGenerationRegistry;
@@ -35,6 +36,7 @@ import art.arcane.volmlib.nativelib.entity.NativeEntityType;
import art.arcane.iris.spi.PlatformRegistries;
import art.arcane.iris.spi.PlatformScheduler;
import art.arcane.iris.spi.PlatformStructureHooks;
import art.arcane.iris.spi.VanillaDataSource;
import art.arcane.volmlib.nativelib.terrain.NativeWorld;
import java.io.File;
@@ -56,6 +58,7 @@ public final class ModdedPlatform implements IrisPlatform {
private final ModdedScheduler scheduler;
private final ModdedStructureHooks structureHooks;
private final ModdedBiomeWriter biomeWriter;
private final ClasspathVanillaDataSource vanillaData;
public ModdedPlatform(NativeModdedLoader loader) {
this.loader = loader;
@@ -65,6 +68,7 @@ public final class ModdedPlatform implements IrisPlatform {
this.scheduler = new ModdedScheduler();
this.structureHooks = new ModdedStructureHooks(ModdedEngineBootstrap::currentServer);
this.biomeWriter = new ModdedBiomeWriter(new NativeBiomeRegistry(ModdedEngineBootstrap::currentServer, "minecraft:plains"));
this.vanillaData = new ClasspathVanillaDataSource(() -> overworldClass(ModdedEngineBootstrap.currentServer()));
}
public static void errorSink(Consumer<Throwable> sink) {
@@ -110,6 +114,11 @@ public final class ModdedPlatform implements IrisPlatform {
return biomeWriter;
}
@Override
public VanillaDataSource vanillaData() {
return vanillaData;
}
@Override
public File dataFolder() {
File folder = loader.configDir().resolve("iris").toFile();
@@ -365,4 +374,15 @@ public final class ModdedPlatform implements IrisPlatform {
"modded-generation-registry-v1");
}
static Class<?> overworldClass(NativeModdedServer server) {
if (server == null) {
throw new IllegalStateException("Vanilla data needs a running Minecraft server.");
}
NativeWorld overworld = server.overworld();
if (overworld == null) {
throw new IllegalStateException("Vanilla data needs the Minecraft server overworld to be loaded.");
}
return overworld.nativeHandle().getClass();
}
}
@@ -189,7 +189,8 @@ final class ModdedCommandHelp {
Entry.command("install", "", ModdedHelpMessages.COMMAND_INSTALL_INSTALL_DIMENSION_TYPE_OVERRIDES_FOR_LOADED_IRIS_DIMENSIONS),
Entry.command("list", "", ModdedHelpMessages.COMMAND_LIST_LIST_CONFIGURED_AND_INSTALLED_DATAPACKS, "ls"),
Entry.command("ingest", "", ModdedHelpMessages.COMMAND_INGEST_EXPLAIN_BUKKIT_DATAPACK_INGEST_WORKFLOW, "pull"),
Entry.command("remove", "<id>", ModdedHelpMessages.COMMAND_REMOVE_EXPLAIN_DATAPACK_REMOVAL_WORKFLOW, "rm")
Entry.command("remove", "<id>", ModdedHelpMessages.COMMAND_REMOVE_EXPLAIN_DATAPACK_REMOVAL_WORKFLOW, "rm"),
Entry.command("export", "<dimension> [version=] [mode=overworld] [namespace=iris_export] [output=] [format=zip] [objects=true] [replace=false]", ModdedHelpMessages.COMMAND_EXPORT_EXPORT_A_DIMENSION_AS_A_VANILLA_WORLDGEN_DATAPACK)
));
SECTIONS.put("datapacks", SECTIONS.get("datapack"));
SECTIONS.put("dp", SECTIONS.get("datapack"));
@@ -18,7 +18,14 @@
package art.arcane.iris.modded.command;
import art.arcane.iris.modded.ModdedEngineBootstrap;
import art.arcane.iris.modded.ModdedIrisLog;
import art.arcane.iris.pack.datapack.export.WorldgenExportProgress;
import art.arcane.iris.pack.datapack.export.WorldgenExportRequest;
import art.arcane.iris.pack.datapack.export.WorldgenExportResult;
import art.arcane.iris.pack.datapack.export.WorldgenExportService;
import art.arcane.iris.pack.datapack.export.WorldgenExportServices;
import art.arcane.iris.pack.datapack.export.ir.ExportDiagnostic;
import art.arcane.iris.pack.loading.IrisData;
import art.arcane.iris.pack.datapack.DataVersion;
import art.arcane.iris.pack.PackDirectoryResolver;
@@ -33,6 +40,7 @@ import com.mojang.brigadier.context.CommandContext;
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeCommandSource;
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeCommandRegistration;
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModdedServer;
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeCommandText;
import art.arcane.volmlib.nativelib.terrain.NativeWorld;
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeWorldDimensions;
@@ -40,6 +48,7 @@ import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
@@ -47,15 +56,20 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.UUID;
import java.util.function.Predicate;
import art.arcane.iris.localization.DirectorCommandMessages;
import art.arcane.iris.localization.IrisLanguage;
import art.arcane.iris.modded.localization.ModdedCommandMessages;
import art.arcane.iris.localization.RuntimeUiMessages;
import art.arcane.volmlib.util.localization.LanguageAudience;
import art.arcane.volmlib.util.localization.MessageArgument;
import art.arcane.volmlib.util.localization.TextKey;
public final class ModdedDatapackCommands {
private static final Predicate<NativeCommandSource> GATE = NativeCommandRegistration.GAMEMASTERS;
private static final String WORLD_PACK_NAME = "iris";
private static final int RESOURCE_REPORTS = 4;
private ModdedDatapackCommands() {
}
@@ -79,6 +93,11 @@ public final class ModdedDatapackCommands {
root.then(message("ingest", "Managed Modrinth datapack ingest is Bukkit-only. On modded servers install the datapack folder or zip in world/datapacks, enable it, restart, then use /iris datapack list to confirm it is enabled. Registered structures generate natively in Iris dimensions."));
root.then(message("pull", "Managed Modrinth datapack ingest is Bukkit-only. On modded servers install the datapack folder or zip in world/datapacks, enable it, restart, then use /iris datapack list to confirm it is enabled. Registered structures generate natively in Iris dimensions."));
root.then(NativeCommandRegistration.literal("export")
.executes(ModdedCommandTree.localized((CommandContext<NativeCommandSource> context) -> export(context.getSource(), "")))
.then(NativeCommandRegistration.argument("args", StringArgumentType.greedyString())
.executes(ModdedCommandTree.localized((CommandContext<NativeCommandSource> context) -> export(context.getSource(), StringArgumentType.getString(context, "args"))))));
root.then(message("remove", "Managed datapack removal is Bukkit-only. On modded servers disable the pack, delete its folder or zip from world/datapacks, and restart."));
root.then(message("rm", "Managed datapack removal is Bukkit-only. On modded servers disable the pack, delete its folder or zip from world/datapacks, and restart."));
@@ -291,4 +310,107 @@ public final class ModdedDatapackCommands {
}
return null;
}
private static int export(NativeCommandSource source, String raw) {
ModdedDatapackExportArguments arguments;
try {
arguments = ModdedDatapackExportArguments.parse(raw);
} catch (IllegalArgumentException invalid) {
IrisModdedCommands.fail(source, IrisLanguage.plain(DirectorCommandMessages.COMMAND_DATAPACK_EXPORT_INVALID, MessageArgument.untrusted("reason", invalid.getMessage())));
return 0;
}
File packsRoot = ModdedPackCommands.packsRoot();
File pack = PackDirectoryResolver.resolveExisting(packsRoot, arguments.dimension());
if (pack == null) {
IrisModdedCommands.fail(source, IrisLanguage.plain(ModdedCommandMessages.MODDED_STUDIO_COMMANDS_PACK_NOT_FOUND_UNDER, MessageArgument.untrusted("name", arguments.dimension()), MessageArgument.untrusted("value", packsRoot.getAbsolutePath())));
return 0;
}
WorldgenExportService service = WorldgenExportServices.get();
WorldgenExportRequest request = arguments.toRequest(pack.toPath(), exportRoot());
IrisModdedCommands.ok(source, IrisLanguage.plain(DirectorCommandMessages.COMMAND_DATAPACK_EXPORT_STARTED, MessageArgument.untrusted("dimension", request.dimension()), MessageArgument.untrusted("version", arguments.target().getVersion())));
ExportFeedback feedback = new ExportFeedback(source);
Thread worker = new Thread(() -> runExport(service, request, feedback), "Iris Datapack Export");
worker.setDaemon(true);
worker.start();
return 1;
}
private static Path exportRoot() {
return ModdedEngineBootstrap.loader().configDir().resolve("irisworldgen").resolve("exports").resolve("datapacks");
}
private static void runExport(WorldgenExportService service, WorldgenExportRequest request, ExportFeedback feedback) {
WorldgenExportResult result;
try {
result = service.export(request, feedback);
} catch (Throwable failure) {
ModdedIrisLog.error("Iris datapack export failed for " + request.dimension(), failure);
feedback.post(() -> IrisModdedCommands.fail(feedback.source, IrisLanguage.plain(DirectorCommandMessages.COMMAND_DATAPACK_EXPORT_FAILED, MessageArgument.untrusted("reason", IrisLanguage.errorDetail(failure)))));
return;
}
feedback.post(() -> reportExport(feedback.source, result));
}
private static void reportExport(NativeCommandSource source, WorldgenExportResult result) {
for (ExportDiagnostic diagnostic : result.diagnostics()) {
String line = IrisLanguage.plain(DirectorCommandMessages.COMMAND_DATAPACK_EXPORT_DIAGNOSTIC, MessageArgument.untrusted("key", diagnosticLabel(diagnostic)), MessageArgument.untrusted("message", diagnostic.message()));
ModdedCommandFeedback.send(source, NativeCommandText.literal(line).withStyle(diagnosticFormat(diagnostic.severity())));
}
if (result.isSuccess()) {
IrisModdedCommands.ok(source, IrisLanguage.plain(DirectorCommandMessages.COMMAND_DATAPACK_EXPORT_EXPORTED, MessageArgument.untrusted("path", result.output())));
return;
}
TextKey outcome = result.status() == WorldgenExportResult.Status.REJECTED
? DirectorCommandMessages.COMMAND_DATAPACK_EXPORT_REJECTED
: DirectorCommandMessages.COMMAND_DATAPACK_EXPORT_FAILED;
IrisModdedCommands.fail(source, IrisLanguage.plain(outcome, MessageArgument.untrusted("reason", result.message())));
}
private static NativeCommandText.Format diagnosticFormat(ExportDiagnostic.Severity severity) {
return switch (severity) {
case ERROR -> NativeCommandText.Format.RED;
case WARNING -> NativeCommandText.Format.YELLOW;
case INFO -> NativeCommandText.Format.GRAY;
};
}
private static String diagnosticLabel(ExportDiagnostic diagnostic) {
return diagnostic.resource().isEmpty()
? diagnostic.limit().name()
: diagnostic.limit().name() + " " + diagnostic.resource();
}
private static final class ExportFeedback implements WorldgenExportProgress {
private final NativeCommandSource source;
private final NativeModdedServer server;
private final UUID audience;
private ExportFeedback(NativeCommandSource source) {
this.source = source;
this.server = source.server();
this.audience = source.playerId();
}
@Override
public void stage(String stage) {
post(() -> progress(IrisLanguage.plain(DirectorCommandMessages.COMMAND_DATAPACK_EXPORT_STAGE, MessageArgument.untrusted("stage", stage))));
}
@Override
public void resources(int done, int total) {
int stride = Math.max(1, total / RESOURCE_REPORTS);
if (done != total && done % stride != 0) {
return;
}
post(() -> progress(IrisLanguage.plain(DirectorCommandMessages.COMMAND_DATAPACK_EXPORT_RESOURCES, MessageArgument.untrusted("done", done), MessageArgument.untrusted("total", total))));
}
private void post(Runnable task) {
server.execute(() -> LanguageAudience.run(audience, task));
}
private void progress(String message) {
ModdedCommandFeedback.send(source, NativeCommandText.literal(message).withStyle(NativeCommandText.Format.GRAY));
}
}
}
@@ -0,0 +1,128 @@
/*
* Iris is a World Generator for Minecraft Servers
* Copyright (c) 2026 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package art.arcane.iris.modded.command;
import art.arcane.iris.pack.datapack.DataVersion;
import art.arcane.iris.pack.datapack.export.DatapackExportDestinations;
import art.arcane.iris.pack.datapack.export.WorldgenExportMode;
import art.arcane.iris.pack.datapack.export.WorldgenExportRequest;
import art.arcane.iris.pack.datapack.export.WorldgenExportTargets;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;
record ModdedDatapackExportArguments(String dimension, String version, String mode, String namespace, String output,
String format, boolean objects, boolean replace) {
private static final Pattern DIMENSION = Pattern.compile("[A-Za-z0-9_/.-]+");
private static final String VERSION_KEY = "version";
private static final String MODE_KEY = "mode";
private static final String NAMESPACE_KEY = "namespace";
private static final String OUTPUT_KEY = "output";
private static final String FORMAT_KEY = "format";
private static final String OBJECTS_KEY = "objects";
private static final String REPLACE_KEY = "replace";
private static final Set<String> OPTIONS = Set.of(VERSION_KEY, MODE_KEY, NAMESPACE_KEY, OUTPUT_KEY, FORMAT_KEY,
OBJECTS_KEY, REPLACE_KEY);
ModdedDatapackExportArguments {
Objects.requireNonNull(dimension, "dimension");
Objects.requireNonNull(version, "version");
Objects.requireNonNull(mode, "mode");
Objects.requireNonNull(namespace, "namespace");
Objects.requireNonNull(output, "output");
Objects.requireNonNull(format, "format");
}
static ModdedDatapackExportArguments parse(String raw) {
String trimmed = raw == null ? "" : raw.trim();
String[] tokens = trimmed.isEmpty() ? new String[0] : trimmed.split("\\s+");
String dimension = null;
Map<String, String> options = new HashMap<>(OPTIONS.size());
for (String token : tokens) {
int equals = token.indexOf('=');
if (equals < 0) {
if (dimension != null) {
throw new IllegalArgumentException("unexpected argument '" + token + "'");
}
dimension = token;
continue;
}
String key = token.substring(0, equals);
if (!OPTIONS.contains(key)) {
throw new IllegalArgumentException("unknown option '" + key + "'");
}
if (options.putIfAbsent(key, token.substring(equals + 1)) != null) {
throw new IllegalArgumentException("option '" + key + "' given twice");
}
}
if (dimension == null) {
throw new IllegalArgumentException("missing dimension");
}
if (!DIMENSION.matcher(dimension).matches() || dimension.contains("..")) {
throw new IllegalArgumentException("invalid dimension key '" + dimension + "'");
}
ModdedDatapackExportArguments arguments = new ModdedDatapackExportArguments(
dimension,
options.getOrDefault(VERSION_KEY, ""),
options.getOrDefault(MODE_KEY, WorldgenExportRequest.DEFAULT_MODE),
options.getOrDefault(NAMESPACE_KEY, WorldgenExportRequest.DEFAULT_NAMESPACE),
options.getOrDefault(OUTPUT_KEY, ""),
options.getOrDefault(FORMAT_KEY, WorldgenExportRequest.DEFAULT_FORMAT),
flag(OBJECTS_KEY, options.get(OBJECTS_KEY), true),
flag(REPLACE_KEY, options.get(REPLACE_KEY), false));
arguments.target();
WorldgenExportMode.parse(arguments.mode());
DatapackExportDestinations.parseFormat(arguments.format());
return arguments;
}
DataVersion target() {
return WorldgenExportTargets.resolve(version);
}
WorldgenExportRequest toRequest(Path pack, Path exportRoot) {
return WorldgenExportRequest.builder()
.pack(pack)
.dimension(dimension)
.version(version)
.mode(mode)
.namespace(namespace)
.output(output)
.format(format)
.objects(objects)
.replace(replace)
.exportRoot(exportRoot)
.build();
}
private static boolean flag(String key, String raw, boolean fallback) {
if (raw == null) {
return fallback;
}
return switch (raw) {
case "true" -> true;
case "false" -> false;
default -> throw new IllegalArgumentException(key + " must be true or false");
};
}
}
@@ -330,6 +330,10 @@ public final class ModdedHelpMessages {
"iris.modded.help.entry.command.remove",
"Explain datapack removal workflow"
);
public static final TextKey COMMAND_EXPORT_EXPORT_A_DIMENSION_AS_A_VANILLA_WORLDGEN_DATAPACK = TextKey.of(
"iris.modded.help.entry.command.export_datapack",
"Export a dimension as a vanilla worldgen datapack"
);
public static final TextKey COMMAND_LIST_REGENERATE_STRUCTURE_INDEX_JSON = TextKey.of(
"iris.modded.help.entry.command.list_structures",
"Regenerate structure-index.json"
@@ -505,6 +509,7 @@ public final class ModdedHelpMessages {
COMMAND_LIST_LIST_CONFIGURED_AND_INSTALLED_DATAPACKS,
COMMAND_INGEST_EXPLAIN_BUKKIT_DATAPACK_INGEST_WORKFLOW,
COMMAND_REMOVE_EXPLAIN_DATAPACK_REMOVAL_WORKFLOW,
COMMAND_EXPORT_EXPORT_A_DIMENSION_AS_A_VANILLA_WORLDGEN_DATAPACK,
COMMAND_LIST_REGENERATE_STRUCTURE_INDEX_JSON,
COMMAND_INFO_RESOLVE_AN_IRIS_STRUCTURE_GRAPH_AND_REPORT_BOUNDS,
COMMAND_PLACE_ASSEMBLE_AND_PLACE_AN_IRIS_STRUCTURE_AT_YOUR_LOCATION,
@@ -0,0 +1,50 @@
package art.arcane.iris.modded;
import art.arcane.iris.platform.ClasspathVanillaDataSource;
import art.arcane.volmlib.nativelib.minecraft26_2.modded.NativeModdedServer;
import art.arcane.volmlib.nativelib.terrain.NativeWorld;
import net.minecraft.SharedConstants;
import net.minecraft.server.Bootstrap;
import net.minecraft.server.level.ServerLevel;
import org.junit.BeforeClass;
import org.junit.Test;
import java.nio.charset.StandardCharsets;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class ModdedVanillaDataTest {
private static final String PLAINS = "data/minecraft/worldgen/biome/plains.json";
private static final String BIOME_TAGS = "data/minecraft/tags/worldgen/biome";
@BeforeClass
public static void bootstrapRegistries() {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
}
@Test
public void overworldLevelClassReadsTheGameVanillaData() {
NativeModdedServer server = mock(NativeModdedServer.class);
NativeWorld overworld = mock(NativeWorld.class);
when(overworld.nativeHandle()).thenReturn(mock(ServerLevel.class));
when(server.overworld()).thenReturn(overworld);
Class<?> anchor = ModdedPlatform.overworldClass(server);
assertSame(ServerLevel.class, anchor);
ClasspathVanillaDataSource source = new ClasspathVanillaDataSource(() -> anchor);
String plains = new String(source.read(PLAINS).orElseThrow(), StandardCharsets.UTF_8);
assertTrue(plains.contains("\"temperature\""));
assertTrue(source.list(BIOME_TAGS).contains(BIOME_TAGS + "/is_overworld.json"));
}
@Test
public void vanillaDataNeedsALoadedOverworld() {
NativeModdedServer server = mock(NativeModdedServer.class);
assertThrows(IllegalStateException.class, () -> ModdedPlatform.overworldClass(null));
assertThrows(IllegalStateException.class, () -> ModdedPlatform.overworldClass(server));
}
}
@@ -63,6 +63,9 @@ public class IrisModdedCommandParityTest {
CommandNode<CommandSourceStack> studio = child(iris, "studio");
child(studio, "package");
child(studio, "pkg");
CommandNode<CommandSourceStack> datapackExport = child(child(iris, "datapack"), "export");
assertNotNull(datapackExport.getCommand());
assertNotNull(child(datapackExport, "args").getCommand());
CommandNode<CommandSourceStack> download = child(iris, "download");
CommandNode<CommandSourceStack> source = child(download, "source");
@@ -211,6 +214,8 @@ public class IrisModdedCommandParityTest {
assertTrue(root.stream().anyMatch((String line) -> line.startsWith("/iris teleport <dimension> [player] (tp) - ")));
assertTrue(ModdedCommandHelp.consoleLines("pregen").stream().anyMatch((String line) ->
line.startsWith("/iris pregen start <radius> [dimension] [at] [x] [z] [gui] [sync] [nocache] - ")));
assertTrue(ModdedCommandHelp.consoleLines("datapack").stream().anyMatch((String line) ->
line.startsWith("/iris datapack export <dimension> [version=] [mode=overworld] [namespace=iris_export] [output=] [format=zip] [objects=true] [replace=false] - ")));
}
private static CommandNode<CommandSourceStack> child(
@@ -0,0 +1,87 @@
package art.arcane.iris.modded.command;
import art.arcane.iris.pack.datapack.DataVersion;
import art.arcane.iris.pack.datapack.export.WorldgenExportRequest;
import org.junit.Test;
import java.nio.file.Path;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
public class ModdedDatapackExportArgumentsTest {
@Test
public void unknownKeyFails() {
assertEquals("unknown option 'seed'", reason("overworld seed=1"));
assertEquals("unknown option 'Version'", reason("overworld Version=26.3"));
}
@Test
public void duplicateKeyFails() {
assertEquals("option 'replace' given twice", reason("overworld replace=true replace=false"));
assertEquals("option 'version' given twice", reason("overworld version= version=26.3"));
}
@Test
public void firstBareTokenIsDimension() {
ModdedDatapackExportArguments arguments = ModdedDatapackExportArguments.parse(
" version=26.3 overworld mode=dimension namespace=worlds output=export_one format=directory objects=false replace=true ");
assertEquals("overworld", arguments.dimension());
assertEquals("26.3", arguments.version());
assertEquals("dimension", arguments.mode());
assertEquals("worlds", arguments.namespace());
assertEquals("export_one", arguments.output());
assertEquals("directory", arguments.format());
assertFalse(arguments.objects());
assertTrue(arguments.replace());
assertSame(DataVersion.V26_3, arguments.target());
assertEquals("unexpected argument 'underworld'", reason("overworld underworld"));
assertEquals("missing dimension", reason(" "));
assertEquals("missing dimension", reason("version=26.3"));
}
@Test
public void defaultsMatchBukkit() {
ModdedDatapackExportArguments arguments = ModdedDatapackExportArguments.parse("overworld");
assertEquals("", arguments.version());
assertEquals("overworld", arguments.mode());
assertEquals("iris_export", arguments.namespace());
assertEquals("", arguments.output());
assertEquals("zip", arguments.format());
assertTrue(arguments.objects());
assertFalse(arguments.replace());
assertSame(DataVersion.V26_3, arguments.target());
Path pack = Path.of("packs", "overworld");
Path exportRoot = Path.of("exports", "datapacks");
WorldgenExportRequest expected = WorldgenExportRequest.builder()
.pack(pack)
.dimension("overworld")
.exportRoot(exportRoot)
.build();
assertEquals(expected, arguments.toRequest(pack, exportRoot));
}
@Test
public void unsupportedValuesFailWithSharedMessages() {
assertEquals("Unsupported datapack export version '1.21.1'. Supported: 26.3",
reason("overworld version=1.21.1"));
assertEquals("Unsupported datapack export version '26.2'. Supported: 26.3",
reason("overworld version=26.2"));
assertEquals("Unknown export mode 'nether'. Expected: preset, dimension, overworld",
reason("overworld mode=nether"));
assertEquals("Export format must be directory or zip.", reason("overworld format=rar"));
assertEquals("objects must be true or false", reason("overworld objects=yes"));
assertEquals("replace must be true or false", reason("overworld replace=1"));
assertEquals("invalid dimension key 'a..b'", reason("a..b"));
}
private static String reason(String raw) {
return assertThrows(IllegalArgumentException.class, () -> ModdedDatapackExportArguments.parse(raw)).getMessage();
}
}