This commit is contained in:
Brian Neumann-Fopiano
2026-08-12 16:31:27 -04:00
parent c59592e448
commit 13e02ecd2f
41 changed files with 352 additions and 449 deletions
@@ -34,9 +34,7 @@ import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.iris.engine.object.IrisWorld;
import art.arcane.iris.engine.platform.BukkitChunkGenerator;
import art.arcane.iris.util.common.plugin.VolmitPlugin;
import art.arcane.iris.util.common.plugin.VolmitSender;
import lombok.NonNull;
import org.bukkit.Bukkit;
import org.bukkit.NamespacedKey;
import org.bukkit.generator.BiomeProvider;
import org.bukkit.generator.ChunkGenerator;
@@ -177,13 +175,8 @@ public final class IrisWorldGeneratorResolver {
+ " but its dimension failed to load; not redownloading. Fix or delete the pack folder.");
return null;
}
Iris.warn("Unable to find dimension type " + id + " Looking for online packs...");
Iris.service(StudioSVC.class).downloadSearch(new VolmitSender(Bukkit.getConsoleSender()), id, false);
dimension = IrisData.loadAnyDimension(id, null);
if (dimension != null) {
Iris.info("Resolved missing dimension, proceeding.");
}
Iris.warn("Unable to find dimension type " + id + ". Install its pack with /iris download "
+ id + " and restart the server.");
}
return dimension;
@@ -710,9 +710,14 @@ public class CommandIris implements DirectorExecutor {
@Param(name = "overwrite", description = "Whether or not to overwrite the pack with the downloaded one", descriptionKey = "iris.director.commandiris.param.whether_not_overwrite_pack_with_downloaded_one", aliases = "force", defaultValue = "false")
boolean overwrite
) {
if (PackDownloader.isManagedBetaPack(pack)) {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_DOWNLOADING_PACK_BETA_RELEASE, MessageArgument.untrusted("pack", pack), MessageArgument.trusted("value", overwrite ? IrisLanguage.text(RuntimeUiMessages.DOWNLOAD_OVERWRITE_SUFFIX) : "")));
Iris.service(StudioSVC.class).downloadManagedBeta(sender(), pack, overwrite);
if (PackDownloader.isDirectZipUrl(pack)) {
sender().sendMessage("Downloading Iris pack from " + pack
+ (overwrite ? IrisLanguage.text(RuntimeUiMessages.DOWNLOAD_OVERWRITE_SUFFIX) : "") + ".");
Iris.service(StudioSVC.class).downloadUrl(sender(), pack, overwrite);
} else if (PackDownloader.isManagedPack(pack)) {
sender().sendMessage("Downloading managed Iris pack '" + pack + "' from its configured Git source"
+ (overwrite ? IrisLanguage.text(RuntimeUiMessages.DOWNLOAD_OVERWRITE_SUFFIX) : "") + ".");
Iris.service(StudioSVC.class).downloadManaged(sender(), pack, overwrite);
} else {
sender().sendMessage(IrisLanguage.text(BukkitCommandMessagesExtended.COMMAND_IRIS_DOWNLOADING_PACK, MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("branch", branch), MessageArgument.trusted("value", overwrite ? IrisLanguage.text(RuntimeUiMessages.DOWNLOAD_OVERWRITE_SUFFIX) : "")));
Iris.service(StudioSVC.class).downloadSearch(sender(), "IrisDimensions/" + pack + "/" + branch, overwrite);
@@ -260,7 +260,7 @@ public final class ModdedEngineBootstrap {
selfTest(moddedLoader.getClass().getClassLoader());
bind();
IrisLanguage.initialize();
ModdedStartup.prefetchDefaultPack();
ModdedStartup.prefetchStartupDatapack();
if (!moddedLoader.clientEnvironment()) {
MainWorldService.reconcileEarly();
}
@@ -253,8 +253,6 @@ public final class ModdedForcedDatapack {
}
private static Path write() throws IOException {
// No ensureDefaultPack() here: write() is reachable from loadPacks on the first boot, and loadPacks
// must never touch the network. ModdedStartup.prefetchDefaultPack covers the download at boot.
String packsHash = packsHash();
Path datapackRoot = datapackRoot();
Files.createDirectories(datapackRoot);
@@ -33,17 +33,15 @@ public final class ModdedModConfig {
private static volatile ModdedModConfig instance;
private final String defaultPack;
private final boolean autoDownloadDefaultPack;
private final String primaryWorld;
private final boolean routePlayersToPrimaryWorld;
private final String mainWorldPack;
private final long mainWorldSeed;
private final boolean mainWorldAutoRestart;
private ModdedModConfig(String defaultPack, boolean autoDownloadDefaultPack, String primaryWorld, boolean routePlayersToPrimaryWorld,
private ModdedModConfig(String defaultPack, String primaryWorld, boolean routePlayersToPrimaryWorld,
String mainWorldPack, long mainWorldSeed, boolean mainWorldAutoRestart) {
this.defaultPack = defaultPack;
this.autoDownloadDefaultPack = autoDownloadDefaultPack;
this.primaryWorld = primaryWorld == null ? "" : primaryWorld.trim();
this.routePlayersToPrimaryWorld = routePlayersToPrimaryWorld;
this.mainWorldPack = mainWorldPack == null ? "" : mainWorldPack.trim();
@@ -68,7 +66,7 @@ public final class ModdedModConfig {
public static void setPrimaryWorld(String dimensionId) {
synchronized (LOCK) {
ModdedModConfig current = get();
ModdedModConfig updated = new ModdedModConfig(current.defaultPack, current.autoDownloadDefaultPack, dimensionId, current.routePlayersToPrimaryWorld,
ModdedModConfig updated = new ModdedModConfig(current.defaultPack, dimensionId, current.routePlayersToPrimaryWorld,
current.mainWorldPack, current.mainWorldSeed, current.mainWorldAutoRestart);
instance = updated;
write(configFile(), updated);
@@ -78,7 +76,7 @@ public final class ModdedModConfig {
public static void setMainWorld(String packRef, long seed) {
synchronized (LOCK) {
ModdedModConfig current = get();
ModdedModConfig updated = new ModdedModConfig(current.defaultPack, current.autoDownloadDefaultPack, current.primaryWorld, current.routePlayersToPrimaryWorld,
ModdedModConfig updated = new ModdedModConfig(current.defaultPack, current.primaryWorld, current.routePlayersToPrimaryWorld,
packRef == null ? "" : packRef.trim(), seed, current.mainWorldAutoRestart);
instance = updated;
write(configFile(), updated);
@@ -89,10 +87,6 @@ public final class ModdedModConfig {
return defaultPack;
}
public boolean autoDownloadDefaultPack() {
return autoDownloadDefaultPack;
}
public String primaryWorld() {
return primaryWorld;
}
@@ -119,7 +113,7 @@ public final class ModdedModConfig {
private static ModdedModConfig load() {
Path file = configFile();
ModdedModConfig defaults = new ModdedModConfig("overworld", true, "", true, "", 0L, false);
ModdedModConfig defaults = new ModdedModConfig("overworld", "", true, "", 0L, false);
if (!Files.isRegularFile(file)) {
write(file, defaults);
return defaults;
@@ -128,7 +122,6 @@ public final class ModdedModConfig {
JSONObject json = new JSONObject(Files.readString(file, StandardCharsets.UTF_8));
return new ModdedModConfig(
json.optString("defaultPack", defaults.defaultPack),
json.optBoolean("autoDownloadDefaultPack", defaults.autoDownloadDefaultPack),
json.optString("primaryWorld", defaults.primaryWorld),
json.optBoolean("routePlayersToPrimaryWorld", defaults.routePlayersToPrimaryWorld),
json.optString("mainWorldPack", defaults.mainWorldPack),
@@ -143,7 +136,6 @@ public final class ModdedModConfig {
private static void write(Path file, ModdedModConfig config) {
JSONObject json = new JSONObject();
json.put("defaultPack", config.defaultPack);
json.put("autoDownloadDefaultPack", config.autoDownloadDefaultPack);
json.put("primaryWorld", config.primaryWorld);
json.put("routePlayersToPrimaryWorld", config.routePlayersToPrimaryWorld);
json.put("mainWorldPack", config.mainWorldPack);
@@ -48,7 +48,7 @@ public final class ModdedPackInstaller {
feedback.accept(IrisLanguage.plain(PackDownloadMessages.INVALID_PACK_NAME, MessageArgument.untrusted("pack", String.valueOf(pack))));
return false;
}
boolean managedBeta = PackDownloader.isManagedBetaPack(pack);
boolean managed = PackDownloader.isManagedPack(pack);
if (!acceptsBranch(pack, branch)) {
feedback.accept(IrisLanguage.plain(PackDownloadMessages.INVALID_BRANCH_NAME, MessageArgument.untrusted("branch", String.valueOf(branch))));
return false;
@@ -58,8 +58,8 @@ public final class ModdedPackInstaller {
synchronized (installLock) {
File packs = configDir.resolve("irisworldgen").resolve("packs").toFile();
try {
PackDownloader.PackInstallResult result = managedBeta
? PackDownloader.downloadManagedBeta(packs, pack, forceOverwrite, feedback)
PackDownloader.PackInstallResult result = managed
? PackDownloader.downloadManaged(packs, pack, forceOverwrite, feedback)
: PackDownloader.download(
packs,
"IrisDimensions/" + pack,
@@ -84,7 +84,7 @@ public final class ModdedPackInstaller {
}
return installed;
} catch (IOException error) {
String source = managedBeta ? "beta release" : "branch " + branch;
String source = managed ? "configured Git source" : "branch " + branch;
LOGGER.error("Iris pack download failed for IrisDimensions/{} ({})", pack, source, error);
feedback.accept(IrisLanguage.plain(
PackDownloadMessages.DOWNLOAD_FAILED,
@@ -97,7 +97,7 @@ public final class ModdedPackInstaller {
}
static boolean acceptsBranch(String pack, String branch) {
return PackDownloader.isManagedBetaPack(pack)
return PackDownloader.isManagedPack(pack)
|| (branch != null && BRANCH_NAME.matcher(branch).matches());
}
@@ -37,7 +37,6 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -47,7 +46,6 @@ public final class ModdedStartup {
private static final Logger LOGGER = LoggerFactory.getLogger("Iris");
private static final AtomicBoolean PREPARED = new AtomicBoolean(false);
private static final AtomicBoolean STARTED = new AtomicBoolean(false);
private static final Object PACK_LOCK = new Object();
private ModdedStartup() {
}
@@ -69,14 +67,13 @@ public final class ModdedStartup {
* ModdedEngineBootstrap.start clears the async queue at SERVER_STARTING, which would silently drop this
* one-shot task, and the datapack must be regenerated before the level PackRepository reload if it can.
*/
public static void prefetchDefaultPack() {
Thread thread = new Thread(ModdedStartup::refreshPacksAndDatapack, "iris-modded-pack-prefetch");
public static void prefetchStartupDatapack() {
Thread thread = new Thread(ModdedStartup::refreshDatapack, "iris-modded-datapack-prefetch");
thread.setDaemon(true);
thread.start();
}
private static void refreshPacksAndDatapack() {
ensureDefaultPack();
private static void refreshDatapack() {
try {
ModdedForcedDatapack.regenerateIfStale("boot");
} catch (Throwable failure) {
@@ -98,10 +95,10 @@ public final class ModdedStartup {
ModdedScheduler scheduler = ModdedEngineBootstrap.schedulerOrNull();
if (scheduler == null) {
refreshPacksAndDatapack();
refreshDatapack();
return;
}
scheduler.async(ModdedStartup::refreshPacksAndDatapack);
scheduler.async(ModdedStartup::refreshDatapack);
}
public static void validateAllPacks() {
@@ -248,45 +245,4 @@ public final class ModdedStartup {
}
}
public static void ensureDefaultPack() {
synchronized (PACK_LOCK) {
ModdedModConfig config = ModdedModConfig.get();
if (!config.autoDownloadDefaultPack()) {
return;
}
Path configDir = ModdedEngineBootstrap.loader().configDir();
File packsRoot = ModdedPackCommands.packsRoot();
for (String pack : startupPacks(config.defaultPack())) {
ensureStartupPack(configDir, packsRoot, pack);
}
}
}
static List<String> startupPacks(String configuredDefault) {
LinkedHashSet<String> packs = new LinkedHashSet<>(PackDownloader.managedBetaPacks());
if (configuredDefault != null && !configuredDefault.isBlank()) {
packs.add(configuredDefault);
}
return List.copyOf(packs);
}
private static void ensureStartupPack(Path configDir, File packsRoot, String pack) {
boolean present = PackDownloader.isManagedBetaPack(pack)
? PackDownloader.isManagedBetaPackPresent(packsRoot, pack)
: PackDownloader.isPackPresent(packsRoot, pack);
if (present) {
return;
}
boolean managedBeta = PackDownloader.isManagedBetaPack(pack);
String branch = managedBeta ? "beta" : "master";
String source = managedBeta ? "beta release" : "master branch";
String role = managedBeta ? "managed beta pack" : "default pack";
LOGGER.info("Iris {} '{}' missing; downloading IrisDimensions/{} ({})", role, pack, pack, source);
boolean installed = ModdedPackInstaller.install(
configDir, pack, branch, false, false,
(String line) -> LOGGER.info("Iris: {}", line));
if (!installed) {
LOGGER.warn("Iris {} '{}' could not be downloaded; install it with /iris download {}", role, pack, pack);
}
}
}
@@ -49,6 +49,8 @@ import net.minecraft.world.level.chunk.ChunkGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -258,8 +260,10 @@ public final class IrisModdedCommands {
static int download(CommandSourceStack source, String pack,
String branch, boolean forceOverwrite) {
boolean managedBeta = PackDownloader.isManagedBetaPack(pack);
String baseDownloadSource = managedBeta ? "beta release" : "branch " + branch;
boolean managed = PackDownloader.isManagedPack(pack);
boolean directUrl = PackDownloader.isDirectZipUrl(pack);
String baseDownloadSource = directUrl ? "direct ZIP URL"
: managed ? "configured Git source" : "branch " + branch;
String downloadSource = forceOverwrite
? baseDownloadSource + IrisLanguage.plain(RuntimeUiMessages.DOWNLOAD_OVERWRITE_SUFFIX)
: baseDownloadSource;
@@ -273,11 +277,28 @@ public final class IrisModdedCommands {
return 0;
}
scheduler.async(() -> {
boolean installed = ModdedPackInstaller.install(
ModdedEngineBootstrap.loader().configDir(), pack, branch, forceOverwrite, true,
(String message) -> scheduler.global(() -> ok(source, message)));
boolean installed;
if (directUrl) {
File packs = ModdedPackCommands.packsRoot();
try {
PackDownloader.PackInstallResult result = PackDownloader.downloadUrl(
packs,
pack,
forceOverwrite,
(String message) -> scheduler.global(() -> ok(source, message))
);
installed = result != null;
} catch (IOException error) {
LOGGER.error("Iris pack download failed for direct URL {}", pack, error);
installed = false;
}
} else {
installed = ModdedPackInstaller.install(
ModdedEngineBootstrap.loader().configDir(), pack, branch, forceOverwrite, false,
(String message) -> scheduler.global(() -> ok(source, message)));
}
if (installed) {
scheduler.global(() -> ok(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_PACK_INSTALLED_ITS_EXACT_DIMENSION_TYPES_CUSTOM_BIOMES_JOIN_FORCED, MessageArgument.untrusted("pack", pack))));
scheduler.global(() -> ok(source, "Pack installed on disk. Restart the server before using it."));
} else {
scheduler.global(() -> fail(source, IrisLanguage.plain(ModdedCommandMessages.IRIS_MODDED_COMMANDS_PACK_DOWNLOAD_FAILED_SEE_CONSOLE, MessageArgument.untrusted("pack", pack), MessageArgument.untrusted("downloadSource", downloadSource))));
}
@@ -26,7 +26,7 @@ import static org.junit.Assert.assertTrue;
public class ModdedPackInstallerTest {
@Test
public void managedBetaPacksIgnoreTheRequestedBranch() {
public void managedPacksIgnoreTheRequestedBranch() {
assertTrue(ModdedPackInstaller.acceptsBranch("overworld", null));
assertTrue(ModdedPackInstaller.acceptsBranch("underworld", "feature/arbitrary"));
}
@@ -1,41 +0,0 @@
/*
* 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;
import org.junit.Test;
import java.util.List;
import static org.junit.Assert.assertEquals;
public class ModdedStartupPackSelectionTest {
@Test
public void managedBetaPacksAreAlwaysInstalledInStableOrder() {
assertEquals(List.of("overworld", "underworld"), ModdedStartup.startupPacks("overworld"));
assertEquals(List.of("overworld", "underworld"), ModdedStartup.startupPacks("underworld"));
}
@Test
public void configuredNonManagedDefaultIsInstalledAfterManagedBetaPacks() {
assertEquals(
List.of("overworld", "underworld", "custom"),
ModdedStartup.startupPacks("custom")
);
}
}