mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-11-01 08:50:06 +00:00
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:
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();
|
||||
}
|
||||
|
||||
}
|
||||
+2
-1
@@ -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"));
|
||||
|
||||
+122
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
+128
@@ -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");
|
||||
};
|
||||
}
|
||||
}
|
||||
+5
@@ -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,
|
||||
|
||||
+50
@@ -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));
|
||||
}
|
||||
}
|
||||
+5
@@ -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(
|
||||
|
||||
+87
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user