Iris downloader / History update

- Allow people to download in place and update in place the media
This commit is contained in:
Brian Neumann-Fopiano
2026-09-18 21:52:19 -04:00
parent b18681a3e2
commit edadd14f4b
40 changed files with 836 additions and 122 deletions
@@ -1465,9 +1465,11 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
private void printPacks() {
File packFolder = Iris.service(StudioSVC.class).getWorkspaceFolder();
for (String line : IrisSplashComposer.composePackLines(packFolder, Iris::reportError)) {
Iris.info(line);
IrisSplashComposer.composePackLines(packFolder, Iris::reportError).thenAccept(lines -> {
if (isEnabled()) {
lines.forEach(Iris::info);
}
});
}
public int getIrisVersion() {
@@ -21,13 +21,11 @@ package art.arcane.iris.command;
import com.google.gson.JsonObject;
import art.arcane.iris.Iris;
import art.arcane.iris.pack.datapack.ServerConfigurator;
import art.arcane.iris.world.lifecycle.LifecycleOperationCoordinator;
import art.arcane.iris.platform.bukkit.nms.datapack.DataVersion;
import art.arcane.iris.world.runtime.ChunkClearer;
import art.arcane.iris.generation.validation.GoldenHashScanner;
import art.arcane.iris.world.runtime.InPlaceChunkRegenerator;
import art.arcane.iris.generation.runtime.IrisEngineSVC;
import art.arcane.iris.studio.StudioSVC;
import art.arcane.iris.pack.IrisPackBenchmarking;
import art.arcane.iris.world.IrisToolbelt;
import art.arcane.iris.generation.runtime.IrisEngineMantle;
@@ -68,7 +66,6 @@ import art.arcane.iris.localization.IrisLanguage;
import art.arcane.iris.localization.BukkitCommandMessages;
import art.arcane.volmlib.util.localization.MessageArgument;
import art.arcane.iris.localization.BukkitCommandMessagesExtended;
import art.arcane.iris.localization.BukkitRuntimeMessages;
@Director(name = "Developer", origin = DirectorOrigin.BOTH, description = "Iris World Manager", descriptionKey = "iris.director.commanddeveloper.director.iris_world_manager", aliases = {"dev"})
public class CommandDeveloper implements DirectorExecutor {
@Director(description = "Get Loaded TectonicPlates Count", descriptionKey = "iris.director.commanddeveloper.director.get_loaded_tectonicplates_count", origin = DirectorOrigin.BOTH, sync = true)
@@ -184,27 +181,7 @@ public class CommandDeveloper implements DirectorExecutor {
@Param(description = "Make sure to make a backup & read the warnings first!", descriptionKey = "iris.director.commanddeveloper.param.make_sure_make_backup_read_warnings_first", defaultValue = "false", aliases = "c")
boolean confirm
) {
if (!confirm) {
sender().sendMessage(IrisLanguage.text(
BukkitRuntimeMessages.COMMAND_DEVELOPER_UPDATE_WORLD_WARNING,
MessageArgument.untrusted("world", world.getName()),
MessageArgument.untrusted("pack", pack.getLoadKey())
));
return;
}
File folder = world.getWorldFolder();
folder.mkdirs();
try (LifecycleOperationCoordinator.Lease lease = LifecycleOperationCoordinator.get().acquire(
LifecycleOperationCoordinator.Domain.PACK_MUTATION,
LifecycleOperationCoordinator.OperationKind.PACK_PUBLISH,
pack.getLoadKey()
)) {
Iris.service(StudioSVC.class).replaceIntoWorld(sender(), pack, folder, world.getSeed());
} catch (LifecycleOperationCoordinator.BusyException e) {
sender().sendMessage(C.YELLOW + e.getMessage());
}
CommandPack.stageWorldUpdate(sender(), world, pack, confirm);
}
@Director(description = "Test", descriptionKey = "iris.director.commanddeveloper.director.test")
@@ -437,7 +437,9 @@ public class CommandIris implements DirectorExecutor {
@Param(name = "pack", description = "The built-in pack to download", descriptionKey = "iris.director.commandiris.param.pack_download", defaultValue = NO_DOWNLOAD_SOURCE, customHandler = DownloadPackHandler.class)
String pack,
@Param(name = "link", description = "A direct HTTP or HTTPS zip link", defaultValue = NO_DOWNLOAD_SOURCE)
String link
String link,
@Param(name = "overwrite", description = "Back up and replace an installed pack", defaultValue = "false")
boolean overwrite
) {
String builtInPack = NO_DOWNLOAD_SOURCE.equals(pack) ? null : pack;
String directLink = NO_DOWNLOAD_SOURCE.equals(link) ? null : link;
@@ -446,14 +448,14 @@ public class CommandIris implements DirectorExecutor {
return;
}
if (builtInPack != null) {
Iris.service(StudioSVC.class).downloadBuiltIn(sender(), builtInPack);
Iris.service(StudioSVC.class).downloadBuiltIn(sender(), builtInPack, overwrite);
return;
}
if (!PackDownloader.isDirectZipUrl(directLink)) {
sender().sendMessage(IrisLanguage.text(PackDownloadMessages.INVALID_URL));
return;
}
Iris.service(StudioSVC.class).downloadUrl(sender(), directLink);
Iris.service(StudioSVC.class).downloadUrl(sender(), directLink, overwrite);
}
@Director(description = "Get metrics for your world", descriptionKey = "iris.director.commandiris.director.get_metrics_your_world", aliases = "measure", origin = DirectorOrigin.PLAYER)
@@ -27,6 +27,9 @@ import art.arcane.iris.pack.PackValidationRegistry;
import art.arcane.iris.pack.PackValidationResult;
import art.arcane.iris.pack.PackValidator;
import art.arcane.iris.studio.StudioSVC;
import art.arcane.iris.world.IrisToolbelt;
import art.arcane.iris.world.lifecycle.LifecycleOperationCoordinator;
import art.arcane.iris.localization.C;
import art.arcane.iris.generation.terrain.IrisDimension;
import art.arcane.iris.command.handlers.DimensionHandler;
import art.arcane.iris.platform.bukkit.plugin.VolmitSender;
@@ -35,6 +38,7 @@ import art.arcane.volmlib.util.director.annotations.Param;
import art.arcane.volmlib.util.localization.TextKey;
import art.arcane.iris.spi.IrisPlatforms;
import org.bukkit.World;
import java.io.File;
import java.io.IOException;
@@ -48,6 +52,42 @@ import art.arcane.volmlib.util.localization.MessageArgument;
import art.arcane.iris.localization.BukkitRuntimeMessages;
@Director(name = "pack", aliases = {"pk"}, description = "Pack validation and maintenance", descriptionKey = "iris.director.commandpack.director.pack_validation_maintenance")
public class CommandPack implements DirectorExecutor {
@Director(name = "update-world", description = "Stage an installed pack for a world and request a clean restart", descriptionKey = "iris.director.commanddeveloper.director.stage_world_generation_update")
public void updateWorld(
@Param(description = "The world to update", descriptionKey = "iris.director.commanddeveloper.param.world_update", contextual = true, contextualOverride = true)
World world,
@Param(description = "The installed dimension to use", descriptionKey = "iris.director.commanddeveloper.param.pack_install_into_world", contextual = true, contextualOverride = true, aliases = "dimension")
IrisDimension pack,
@Param(description = "Confirm that the complete world has been backed up", descriptionKey = "iris.director.commanddeveloper.param.make_sure_make_backup_read_warnings_first", defaultValue = "false", aliases = "c")
boolean confirm
) {
stageWorldUpdate(sender(), world, pack, confirm);
}
static void stageWorldUpdate(VolmitSender sender, World world, IrisDimension pack, boolean confirm) {
if (!IrisToolbelt.isIrisWorld(world) || IrisToolbelt.isIrisStudioWorld(world)) {
sender.sendMessage(IrisLanguage.text(BukkitRuntimeMessages.COMMAND_PACK_UPDATE_WORLD_REQUIRES_PERSISTENT_WORLD));
return;
}
if (!confirm) {
sender.sendMessage(IrisLanguage.text(
BukkitRuntimeMessages.COMMAND_DEVELOPER_UPDATE_WORLD_WARNING,
MessageArgument.untrusted("world", world.getName()),
MessageArgument.untrusted("pack", pack.getLoadKey())
));
return;
}
try (LifecycleOperationCoordinator.Lease lease = LifecycleOperationCoordinator.get().acquire(
LifecycleOperationCoordinator.Domain.PACK_MUTATION,
LifecycleOperationCoordinator.OperationKind.PACK_PUBLISH,
world.getName()
)) {
Iris.service(StudioSVC.class).replaceIntoWorld(sender, pack, world.getWorldFolder(), world.getSeed());
} catch (LifecycleOperationCoordinator.BusyException e) {
sender.sendMessage(C.YELLOW + e.getMessage());
}
}
/**
* Director treats a blank defaultValue as "required", which made the all-packs branch
* unreachable from chat. The "*" sentinel keeps the parameter genuinely optional; a blank
@@ -15,22 +15,25 @@ import static org.junit.Assert.assertTrue;
public class CommandIrisDownloadContractTest {
@Test
public void commandExposesOnlyPackAndLinkParameters() throws NoSuchMethodException {
Method method = CommandIris.class.getDeclaredMethod("download", String.class, String.class);
public void commandRequiresExplicitOverwriteForPackReplacement() throws NoSuchMethodException {
Method method = CommandIris.class.getDeclaredMethod("download", String.class, String.class, boolean.class);
Parameter[] parameters = method.getParameters();
Param pack = parameters[0].getAnnotation(Param.class);
Param link = parameters[1].getAnnotation(Param.class);
Param overwrite = parameters[2].getAnnotation(Param.class);
List<Method> downloadMethods = Arrays.stream(CommandIris.class.getDeclaredMethods())
.filter((Method candidate) -> candidate.getName().equals("download"))
.toList();
assertEquals(1, downloadMethods.size());
assertEquals(2, downloadMethods.getFirst().getParameterCount());
assertEquals(3, downloadMethods.getFirst().getParameterCount());
assertEquals("pack", pack.name());
assertEquals(0L, Arrays.stream(pack.aliases()).filter((String alias) -> !alias.isBlank()).count());
assertEquals(CommandIris.DownloadPackHandler.class, pack.customHandler());
assertEquals("link", link.name());
assertEquals(0L, Arrays.stream(link.aliases()).filter((String alias) -> !alias.isBlank()).count());
assertEquals("overwrite", overwrite.name());
assertEquals("false", overwrite.defaultValue());
}
@Test
@@ -0,0 +1,108 @@
package art.arcane.iris.command;
import art.arcane.iris.Iris;
import art.arcane.iris.generation.terrain.IrisDimension;
import art.arcane.iris.localization.BukkitRuntimeMessages;
import art.arcane.iris.localization.IrisLanguage;
import art.arcane.iris.platform.bukkit.plugin.VolmitSender;
import art.arcane.iris.studio.StudioSVC;
import art.arcane.iris.world.IrisToolbelt;
import art.arcane.volmlib.util.director.compat.DirectorAnnotationCompatibility;
import art.arcane.volmlib.util.director.runtime.DirectorNodeDescriptor;
import art.arcane.volmlib.util.localization.MessageArgument;
import art.arcane.volmlib.util.localization.TextKey;
import org.bukkit.World;
import org.junit.Test;
import org.mockito.MockedStatic;
import java.io.File;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class CommandPackUpdateWorldTest {
@Test
public void packNamespaceExposesAnExplicitConfirmation() throws Exception {
DirectorNodeDescriptor command = DirectorAnnotationCompatibility.fromMethod(
CommandPack.class.getDeclaredMethod("updateWorld", World.class, IrisDimension.class, boolean.class)
).orElseThrow();
assertEquals("update-world", command.getName());
assertTrue(command.getParameters().get(0).isRequired());
assertTrue(command.getParameters().get(1).isRequired());
assertFalse(command.getParameters().get(2).isRequired());
assertEquals("false", command.getParameters().get(2).getDefaultValue());
}
@Test
public void unconfirmedAndNonPersistentWorldUpdatesCannotPublish() {
VolmitSender sender = mock(VolmitSender.class);
World world = mock(World.class);
IrisDimension pack = new IrisDimension();
pack.setLoadKey("overworld");
when(world.getName()).thenReturn("example");
try (MockedStatic<Iris> iris = mockStatic(Iris.class);
MockedStatic<IrisToolbelt> toolbelt = mockStatic(IrisToolbelt.class)) {
CommandPack.stageWorldUpdate(sender, world, pack, true);
toolbelt.when(() -> IrisToolbelt.isIrisWorld(world)).thenReturn(true);
toolbelt.when(() -> IrisToolbelt.isIrisStudioWorld(world)).thenReturn(true);
CommandPack.stageWorldUpdate(sender, world, pack, true);
toolbelt.when(() -> IrisToolbelt.isIrisStudioWorld(world)).thenReturn(false);
CommandPack.stageWorldUpdate(sender, world, pack, false);
iris.verifyNoInteractions();
verify(sender, times(2)).sendMessage(IrisLanguage.text(
BukkitRuntimeMessages.COMMAND_PACK_UPDATE_WORLD_REQUIRES_PERSISTENT_WORLD));
verify(sender).sendMessage(IrisLanguage.text(
BukkitRuntimeMessages.COMMAND_DEVELOPER_UPDATE_WORLD_WARNING,
MessageArgument.untrusted("world", "example"),
MessageArgument.untrusted("pack", "overworld")));
}
}
@Test
public void updateResultsRenderTheWorldPackAndVersion() {
for (TextKey key : List.of(BukkitRuntimeMessages.COMMAND_PACK_UPDATE_WORLD_STAGED,
BukkitRuntimeMessages.COMMAND_PACK_UPDATE_WORLD_UNCHANGED)) {
String message = IrisLanguage.text(key,
MessageArgument.untrusted("world", "example"),
MessageArgument.untrusted("pack", "overworld"),
MessageArgument.untrusted("version", 4011));
assertTrue(message.contains("example"));
assertTrue(message.contains("overworld"));
assertTrue(message.contains("4011"));
assertFalse(message.contains("{world}"));
assertFalse(message.contains("{pack}"));
assertFalse(message.contains("{version}"));
}
}
@Test
public void confirmedUpdateUsesTheExistingWorldFolderAndSeed() {
VolmitSender sender = mock(VolmitSender.class);
World world = mock(World.class);
IrisDimension pack = new IrisDimension();
File folder = new File("example");
StudioSVC studio = mock(StudioSVC.class);
when(world.getName()).thenReturn("example");
when(world.getWorldFolder()).thenReturn(folder);
when(world.getSeed()).thenReturn(42L);
try (MockedStatic<Iris> iris = mockStatic(Iris.class);
MockedStatic<IrisToolbelt> toolbelt = mockStatic(IrisToolbelt.class)) {
iris.when(() -> Iris.service(StudioSVC.class)).thenReturn(studio);
toolbelt.when(() -> IrisToolbelt.isIrisWorld(world)).thenReturn(true);
CommandPack.stageWorldUpdate(sender, world, pack, true);
verify(studio).replaceIntoWorld(sender, pack, folder, 42L);
}
}
}
@@ -40,9 +40,8 @@ public final class ModdedIrisSplash {
private static void printPacks(ModdedLoader loader) {
File packFolder = IrisPlatforms.get().packsFolderNoCreate();
for (String line : IrisSplashComposer.composePackLines(packFolder, IrisLogging::reportError)) {
IrisLogging.info(line);
}
IrisSplashComposer.composePackLines(packFolder, IrisLogging::reportError)
.thenAccept(lines -> lines.forEach(IrisLogging::info));
}
private static void printLogo(ModdedLoader loader) {
@@ -406,14 +406,14 @@ public final class IrisModdedCommands {
? PackDownloader.downloadUrl(
packs,
request.url(),
false,
request.overwrite(),
(String message) -> dispatchDownloadFeedback(source, () -> ok(source, message)),
cancellation
)
: PackDownloader.downloadBuiltIn(
packs,
request.pack(),
false,
request.overwrite(),
(String message) -> dispatchDownloadFeedback(source, () -> ok(source, message)),
cancellation
);
@@ -478,16 +478,34 @@ public final class IrisModdedCommands {
if (rawRequest == null || rawRequest.isBlank()) {
return null;
}
if (rawRequest.startsWith("pack=")) {
String pack = rawRequest.substring("pack=".length()).trim().toLowerCase(Locale.ROOT);
return PackDownloader.isBuiltInPack(pack) ? new DownloadRequest(pack, null) : null;
}
if (rawRequest.startsWith("link=")) {
String url = rawRequest.substring("link=".length()).trim();
return PackDownloader.isDirectZipUrl(url) ? new DownloadRequest(null, url) : null;
}
String pack = null;
String url = null;
Boolean overwrite = null;
for (String argument : rawRequest.trim().split("\\s+")) {
if (argument.startsWith("pack=") && pack == null) {
pack = argument.substring("pack=".length()).toLowerCase(Locale.ROOT);
if (!PackDownloader.isBuiltInPack(pack)) {
return null;
}
} else if (argument.startsWith("link=") && url == null) {
url = argument.substring("link=".length());
if (!PackDownloader.isDirectZipUrl(url)) {
return null;
}
} else if (argument.startsWith("overwrite=") && overwrite == null) {
String value = argument.substring("overwrite=".length());
if (!"true".equalsIgnoreCase(value) && !"false".equalsIgnoreCase(value)) {
return null;
}
overwrite = Boolean.parseBoolean(value);
} else {
return null;
}
}
return (pack == null) == (url == null)
? null
: new DownloadRequest(pack, url, Boolean.TRUE.equals(overwrite));
}
static int metrics(CommandSourceStack source) {
ServerLevel level = source.getLevel();
@@ -555,6 +573,6 @@ public final class IrisModdedCommands {
ModdedCommandFeedback.fail(source, message);
}
record DownloadRequest(String pack, String url) {
record DownloadRequest(String pack, String url, boolean overwrite) {
}
}
@@ -70,7 +70,7 @@ final class ModdedCommandHelp {
Entry.command("seed", "", ModdedHelpMessages.COMMAND_SEED_PRINT_WORLD_AND_ENGINE_SEED_INFORMATION),
Entry.command("debug", "", ModdedHelpMessages.COMMAND_DEBUG_TOGGLE_IRIS_DEBUG_LOGGING_AND_SAVE_SETTINGS_JSON),
Entry.command("reload", "", ModdedHelpMessages.COMMAND_RELOAD_RELOAD_SETTINGS_JSON_ALSO_HOTLOADED_AUTOMATICALLY_EVERY_3S),
Entry.command("download", "<pack=overworld|pack=underworld|link=<zip-url>>", ModdedHelpMessages.COMMAND_DOWNLOAD_DOWNLOAD_A_PACK_PROJECT, "dl"),
Entry.command("download", "<pack=overworld|pack=underworld|link=<zip-url>> [overwrite=true]", ModdedHelpMessages.COMMAND_DOWNLOAD_DOWNLOAD_A_PACK_PROJECT, "dl"),
Entry.command("metrics", "", ModdedHelpMessages.COMMAND_METRICS_PRINT_GENERATION_METRICS_FOR_YOUR_CURRENT_IRIS_DIMENSION, "measure"),
Entry.command("regen", "[radius]", ModdedHelpMessages.COMMAND_REGEN_DELETE_AND_REGENERATE_NEARBY_CHUNKS_IN_PLACE, "rg"),
Entry.group("pregen", ModdedHelpMessages.GROUP_PREGEN_PREGENERATE_AN_IRIS_DIMENSION, "pregenerate"),
@@ -43,7 +43,7 @@ final class ModdedCommandTree {
private static final SuggestionProvider<CommandSourceStack> DOWNLOAD_SOURCES =
(context, builder) ->
SharedSuggestionProvider.suggest(
List.of("pack=overworld", "pack=underworld", "link="),
List.of("pack=overworld", "pack=underworld", "link=", "pack=overworld overwrite=true", "pack=underworld overwrite=true"),
builder
);
private static final Predicate<CommandSourceStack> GATE = Commands.hasPermission(Commands.LEVEL_GAMEMASTERS);
@@ -89,7 +89,27 @@ public class IrisModdedCommandParityTest {
assertNull(IrisModdedCommands.parseDownloadRequest("pack=custom"));
assertNull(IrisModdedCommands.parseDownloadRequest("link=https://packs.example.test/custom.tar.gz"));
assertNull(IrisModdedCommands.parseDownloadRequest("pack=underworld branch=stable"));
assertNull(IrisModdedCommands.parseDownloadRequest("pack=underworld overwrite=true"));
assertFalse(overworld.overwrite());
}
@Test
public void downloadOverwriteRequiresOneSourceAndAnExplicitBoolean() {
IrisModdedCommands.DownloadRequest pack = IrisModdedCommands.parseDownloadRequest("pack=underworld overwrite=true");
IrisModdedCommands.DownloadRequest link = IrisModdedCommands.parseDownloadRequest(
"overwrite=true link=https://packs.example.test/custom.zip?token=a=b");
assertNotNull(pack);
assertTrue(pack.overwrite());
assertEquals("underworld", pack.pack());
assertNotNull(link);
assertTrue(link.overwrite());
assertEquals("https://packs.example.test/custom.zip?token=a=b", link.url());
assertFalse(IrisModdedCommands.parseDownloadRequest("pack=overworld overwrite=false").overwrite());
assertNull(IrisModdedCommands.parseDownloadRequest("overwrite=true"));
assertNull(IrisModdedCommands.parseDownloadRequest("pack=overworld overwrite=yes"));
assertNull(IrisModdedCommands.parseDownloadRequest("pack=overworld overwrite=true overwrite=false"));
assertNull(IrisModdedCommands.parseDownloadRequest("pack=overworld pack=underworld"));
assertNull(IrisModdedCommands.parseDownloadRequest("pack=overworld link=https://packs.example.test/custom.zip"));
}
@Test
@@ -1,12 +1,15 @@
package art.arcane.iris.diagnostics.splash;
import art.arcane.iris.BuildConstants;
import art.arcane.iris.pack.BuiltInPackUpdates;
import java.io.File;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
public final class IrisSplashComposer {
private static final String SPLASH_PADDING = " ".repeat(4);
@@ -40,8 +43,14 @@ public final class IrisSplashComposer {
return builder.toString();
}
public static List<String> composePackLines(File packFolder, IrisSplashPackScanner.SplashPackErrorReporter reporter) {
public static CompletableFuture<List<String>> composePackLines(File packFolder, IrisSplashPackScanner.SplashPackErrorReporter reporter) {
List<IrisSplashPackScanner.SplashPackMetadata> packs = IrisSplashPackScanner.collect(packFolder, reporter);
return BuiltInPackUpdates.check(packs.stream().map(IrisSplashPackScanner.SplashPackMetadata::name).toList())
.thenApply(updates -> composePackLines(packs, updates));
}
static List<String> composePackLines(List<IrisSplashPackScanner.SplashPackMetadata> packs,
Map<String, BuiltInPackUpdates.Update> updates) {
if (packs.isEmpty()) {
return List.of();
}
@@ -49,7 +58,15 @@ public final class IrisSplashComposer {
List<String> lines = new ArrayList<>(packs.size() + 1);
lines.add("Custom Dimensions: " + packs.size());
for (IrisSplashPackScanner.SplashPackMetadata pack : packs) {
lines.add(" " + pack.name() + " v" + pack.version());
BuiltInPackUpdates.Update update = updates.get(pack.name());
lines.add(" " + pack.name() + " v" + pack.version()
+ (update == null ? "" : update.suffix(pack.version())));
}
for (IrisSplashPackScanner.SplashPackMetadata pack : packs) {
BuiltInPackUpdates.Update update = updates.get(pack.name());
if (update != null && update.newerThan(pack.version())) {
lines.add("Update " + pack.name() + ": /iris download pack=" + pack.name() + " overwrite=true");
}
}
return lines;
}
@@ -180,6 +180,18 @@ public final class BukkitRuntimeMessages {
C.RED + "To stage the update and request a clean restart:",
C.RED + "/iris developer update-world {world} {pack} confirm=true"
);
public static final TextKey COMMAND_PACK_UPDATE_WORLD_REQUIRES_PERSISTENT_WORLD = TextKey.of(
"iris.bukkit.runtime.commandpack.update_world_requires_persistent_world",
C.RED + "Choose a loaded Iris world. Studio worlds cannot receive generation updates."
);
public static final TextKey COMMAND_PACK_UPDATE_WORLD_STAGED = TextKey.of(
"iris.bukkit.runtime.commandpack.update_world_staged",
C.GREEN + "Staged {pack} v{version} for world '{world}'. A clean restart will activate the pack with terrain blending."
);
public static final TextKey COMMAND_PACK_UPDATE_WORLD_UNCHANGED = TextKey.of(
"iris.bukkit.runtime.commandpack.update_world_unchanged",
C.GREEN + "World '{world}' already uses {pack} v{version} with the same generation settings. No restart is needed."
);
public static final TextKey BULK_STRUCTURE_IMPORTER_IMPORTING_VANILLA_DATAPACK_STRUCTURES_MODE_INCLUDENONJIGSAW = TextKey.of(
"iris.bukkit.runtime.bulkstructureimporter.importing_vanilla_datapack_structures_mode_includenonjigsaw",
C.GREEN + "Importing " + C.WHITE + "{total}" + C.GREEN + " vanilla & datapack structures (mode=" + "{mode}" + ", includeNonJigsaw=" + "{includeNonJigsaw}" + ")..."
@@ -658,6 +670,9 @@ public final class BukkitRuntimeMessages {
COMMAND_PACK_COMPAT_NONE,
COMMAND_PACK_COMPAT_REMEDY,
COMMAND_DEVELOPER_UPDATE_WORLD_WARNING,
COMMAND_PACK_UPDATE_WORLD_REQUIRES_PERSISTENT_WORLD,
COMMAND_PACK_UPDATE_WORLD_STAGED,
COMMAND_PACK_UPDATE_WORLD_UNCHANGED,
BULK_STRUCTURE_IMPORTER_IMPORTING_VANILLA_DATAPACK_STRUCTURES_MODE_INCLUDENONJIGSAW,
BULK_STRUCTURE_IMPORTER_FAIL_INVALID_KEY,
BULK_STRUCTURE_IMPORTER_JIGSAW,
@@ -39,7 +39,7 @@ public final class PackDownloadMessages {
);
public static final TextKey PROGRESS_UNCHANGED = TextKey.of(
"iris.runtime.pack_download.progress.unchanged",
C.YELLOW + "Iris pack '{pack}' is already installed."
C.YELLOW + "Iris pack '{pack}' is already installed. Use overwrite=true to back up and replace it."
);
public static final TextKey PROGRESS_FAILED = TextKey.of(
"iris.runtime.pack_download.progress.failed",
@@ -99,6 +99,14 @@ public final class PackDownloadMessages {
"iris.runtime.pack_download.shutting_down",
C.YELLOW + "Iris is shutting down and is not accepting pack downloads."
);
public static final TextKey CLOSE_STUDIO = TextKey.of(
"iris.runtime.pack_download.close_studio",
"Close the active Studio project before replacing an installed pack."
);
public static final TextKey BACKUP_RETAINED = TextKey.of(
"iris.runtime.pack_download.backup_retained",
"Previous pack saved to {path}."
);
public static final TextKey DOWNLOADING = TextKey.of(
"iris.runtime.pack_download.downloading",
"Downloading {url}"
@@ -150,7 +158,7 @@ public final class PackDownloadMessages {
);
public static final TextKey PACK_KEY_CONFLICT = TextKey.of(
"iris.runtime.pack_download.pack_key_conflict",
"Another pack is using the key {key}. Import failed!"
"Pack {key} is already installed. Use overwrite=true to back up and replace it."
);
public static final TextKey ACQUIRED = TextKey.of(
"iris.runtime.pack_download.acquired",
@@ -158,7 +166,7 @@ public final class PackDownloadMessages {
);
public static final TextKey ALREADY_INSTALLED = TextKey.of(
"iris.runtime.pack_download.already_installed",
"Pack {key} is already installed, skipping download."
"Pack {key} is already installed, skipping download. Use overwrite=true to back up and replace it."
);
public static final TextKey IN_PROGRESS = TextKey.of(
"iris.runtime.pack_download.in_progress",
@@ -207,6 +215,8 @@ public final class PackDownloadMessages {
INVALID_URL,
INVALID_BUILT_IN,
SHUTTING_DOWN,
CLOSE_STUDIO,
BACKUP_RETAINED,
DOWNLOADING,
UNPACKING,
UNPACK_FAILED,
@@ -71,7 +71,7 @@ public final class AtomicDirectoryPublisher {
public static final class Publication implements AutoCloseable {
private final Path target;
private final Path backup;
private Path backup;
private boolean committed;
private boolean closed;
@@ -87,6 +87,22 @@ public final class AtomicDirectoryPublisher {
committed = true;
}
public synchronized boolean retainBackup(Path destination) throws IOException {
if (closed || committed) {
throw new IllegalStateException("Directory publication is already finalized.");
}
if (backup == null) {
return false;
}
Path retained = Objects.requireNonNull(destination, "destination").toAbsolutePath().normalize();
if (Files.exists(retained) || Files.isSymbolicLink(retained)) {
throw new FileAlreadyExistsException(retained.toString());
}
move(backup, retained);
backup = retained;
return true;
}
public synchronized void cleanupBackup() throws IOException {
if (!committed) {
throw new IllegalStateException("Directory publication is not committed.");
@@ -0,0 +1,112 @@
package art.arcane.iris.pack;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.math.BigInteger;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.regex.Pattern;
public final class BuiltInPackUpdates {
private static final Duration REQUEST_TIMEOUT = Duration.ofSeconds(5);
private static final Pattern VERSION = Pattern.compile("[0-9]+");
private static final HttpClient CLIENT = HttpClient.newBuilder()
.connectTimeout(REQUEST_TIMEOUT)
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
private BuiltInPackUpdates() {
}
public static CompletableFuture<Map<String, Update>> check(List<String> packs) {
return check(packs, CLIENT, pack -> URI.create(
"https://api.github.com/repos/IrisDimensions/" + pack + "/releases/latest"));
}
static CompletableFuture<Map<String, Update>> check(List<String> packs, HttpClient client,
Function<String, URI> endpoint) {
Map<String, CompletableFuture<Update>> requests = new LinkedHashMap<>();
for (String pack : packs) {
if (!PackDownloader.isBuiltInPack(pack) || requests.containsKey(pack)) {
continue;
}
HttpRequest request = HttpRequest.newBuilder(endpoint.apply(pack))
.timeout(REQUEST_TIMEOUT)
.header("Accept", "application/vnd.github+json")
.header("User-Agent", "Iris-Pack-Updates")
.GET()
.build();
CompletableFuture<HttpResponse<String>> response = client.sendAsync(request,
HttpResponse.BodyHandlers.limiting(HttpResponse.BodyHandlers.ofString(), 1024L * 1024L));
requests.put(pack, response.copy().orTimeout(REQUEST_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS)
.whenComplete((result, error) -> {
if (error != null) {
response.cancel(true);
}
})
.thenApply(result -> readRelease(pack, result))
.exceptionally(error -> Update.unavailable()));
}
return CompletableFuture.allOf(requests.values().toArray(CompletableFuture[]::new))
.thenApply(ignored -> {
Map<String, Update> updates = new LinkedHashMap<>();
requests.forEach((pack, request) -> updates.put(pack, request.join()));
return Map.copyOf(updates);
});
}
private static Update readRelease(String pack, HttpResponse<String> response) {
if (response.statusCode() != 200) {
return Update.unavailable();
}
JsonObject release = JsonParser.parseString(response.body()).getAsJsonObject();
if (release.get("draft").getAsBoolean() || release.get("prerelease").getAsBoolean()) {
return Update.unavailable();
}
String version = release.get("tag_name").getAsString().replaceFirst("^[vV]\\+?", "");
if (!VERSION.matcher(version).matches()) {
return Update.unavailable();
}
for (JsonElement element : release.getAsJsonArray("assets")) {
JsonObject asset = element.getAsJsonObject();
if ((pack + ".zip").equals(asset.get("name").getAsString())) {
return new Update(version);
}
}
return Update.unavailable();
}
public record Update(String version) {
public static Update unavailable() {
return new Update(null);
}
public boolean newerThan(String installedVersion) {
return version != null && VERSION.matcher(installedVersion).matches()
&& new BigInteger(version).compareTo(new BigInteger(installedVersion)) > 0;
}
public String suffix(String installedVersion) {
if (version == null) {
return " (update check unavailable)";
}
if (!VERSION.matcher(installedVersion).matches()) {
return " (latest v" + version + ")";
}
return newerThan(installedVersion)
? " -> v" + version + " available"
: "";
}
}
}
@@ -646,8 +646,21 @@ public final class PackDownloader {
}
IrisData.getLoaded(new File(packsFolder, prepared.key())).ifPresent(IrisData::close);
IrisData.getLoaded(target.toFile()).ifPresent(IrisData::close);
Path retainedBackup = null;
if (forceOverwrite && Files.exists(target)) {
Path backupRoot = packsRoot.resolve(".backups");
if (Files.isSymbolicLink(backupRoot)) {
throw new IOException("Pack backup folder is an unsafe symbolic link: " + backupRoot);
}
Files.createDirectories(backupRoot);
retainedBackup = backupRoot.resolve(prepared.key() + "-" + UUID.randomUUID());
}
try (AtomicDirectoryPublisher.Publication publication = AtomicDirectoryPublisher.publish(staging, target)) {
if (retainedBackup != null) {
publication.retainBackup(retainedBackup);
}
publication.commit();
if (retainedBackup == null) {
try {
publication.cleanupBackup();
} catch (IOException exception) {
@@ -657,6 +670,13 @@ public final class PackDownloader {
);
}
}
}
if (retainedBackup != null) {
sendFeedback(feedback, IrisLanguage.plain(
PackDownloadMessages.BACKUP_RETAINED,
MessageArgument.untrusted("path", retainedBackup.toString())
));
}
PackValidationRegistry.publish(prepared.validation());
sendValidationFeedback(prepared.validation(), feedback);
sendFeedback(feedback, IrisLanguage.plain(
@@ -290,6 +290,16 @@ public class StudioSVC implements IrisService {
? history.packRoot(history.pendingActivation().orElseThrow().activationId())
: history.activePackRoot();
IrisDimension installed = loadInstalledDimension(installedPack, source, dimensionKey);
if (stageUpdate) {
sender.sendMessage(IrisLanguage.text(
restartRequired
? BukkitRuntimeMessages.COMMAND_PACK_UPDATE_WORLD_STAGED
: BukkitRuntimeMessages.COMMAND_PACK_UPDATE_WORLD_UNCHANGED,
MessageArgument.untrusted("world", dimensionRoot.getName()),
MessageArgument.untrusted("pack", installed.getLoadKey()),
MessageArgument.untrusted("version", installed.getVersion())
));
}
if (restartRequired) {
ServerConfigurator.restart("An Iris generation epoch update is pending activation.");
}
@@ -352,6 +362,7 @@ public class StudioSVC implements IrisService {
packRoot,
GenerationPackFingerprint.CURRENT_VERSION
);
validatePublishedPack(packRoot);
IrisData data = IrisData.openDatapackCompiler(packRoot.toFile());
try {
IrisDimension dimension = data.getDimensionLoader().load(dimensionKey, false);
@@ -378,6 +389,10 @@ public class StudioSVC implements IrisService {
fingerprint,
aliasPolicy
);
if (!fingerprint.equals(GenerationPackFingerprint.compute(
packRoot, GenerationPackFingerprint.CURRENT_VERSION))) {
throw new IOException("Generation pack changed during validation: " + packRoot);
}
return new GenerationCandidate(fingerprint, dimensionContract, registryContract);
} finally {
data.close();
@@ -628,14 +643,15 @@ public class StudioSVC implements IrisService {
return replaceIntoPackDirectory(sender, dimension, folder);
}
public void downloadBuiltIn(VolmitSender sender, String key) {
public void downloadBuiltIn(VolmitSender sender, String key, boolean overwrite) {
if (!PackDownloader.isBuiltInPack(key)) {
sender.sendMessage(IrisLanguage.text(PackDownloadMessages.INVALID_BUILT_IN));
return;
}
PackDownloadProgressReporter reporter = new PackDownloadProgressReporter(sender, key);
runPackMutation(sender, LifecycleOperationCoordinator.OperationKind.PACK_DOWNLOAD, key, reporter, cancellation -> {
PackDownloader.PackInstallResult result = downloadBuiltInLocked(key, cancellation, reporter);
requireClosedStudioForPackOverwrite(overwrite);
PackDownloader.PackInstallResult result = downloadBuiltInLocked(key, overwrite, cancellation, reporter);
if (result == null) {
reporter.fail(null);
return;
@@ -645,7 +661,7 @@ public class StudioSVC implements IrisService {
}, "Failed to download built-in Iris pack '" + key + "'.");
}
public void downloadUrl(VolmitSender sender, String url) {
public void downloadUrl(VolmitSender sender, String url, boolean overwrite) {
if (!PackDownloader.isDirectZipUrl(url)) {
sender.sendMessage(IrisLanguage.text(PackDownloadMessages.INVALID_URL));
return;
@@ -656,10 +672,11 @@ public class StudioSVC implements IrisService {
url
);
runPackMutation(sender, LifecycleOperationCoordinator.OperationKind.PACK_DOWNLOAD, "remote-zip", reporter, cancellation -> {
requireClosedStudioForPackOverwrite(overwrite);
PackDownloader.PackInstallResult result = PackDownloader.downloadUrl(
getWorkspaceFolder(),
url,
false,
overwrite,
reporter::detail,
cancellation,
reporter
@@ -675,23 +692,30 @@ public class StudioSVC implements IrisService {
private PackDownloader.PackInstallResult downloadBuiltInLocked(
String expectedKey,
boolean overwrite,
PackDownloader.DownloadCancellation cancellation,
PackDownloadProgressReporter reporter
) throws IOException {
if (PackDownloader.isBuiltInPackPresent(getWorkspaceFolder(), expectedKey)) {
if (!overwrite && PackDownloader.isBuiltInPackPresent(getWorkspaceFolder(), expectedKey)) {
return new PackDownloader.PackInstallResult(expectedKey, false, false);
}
return PackDownloader.downloadBuiltIn(
getWorkspaceFolder(),
expectedKey,
false,
overwrite,
reporter::detail,
cancellation,
reporter
);
}
private void requireClosedStudioForPackOverwrite(boolean overwrite) throws IOException {
if (overwrite && activeProject != null) {
throw new IOException(IrisLanguage.plain(PackDownloadMessages.CLOSE_STUDIO));
}
}
public boolean isProjectOpen() {
return activeProject != null && activeProject.isOpen();
}
+10 -3
View File
@@ -1577,6 +1577,10 @@ failed_fragment = " fehlgeschlagen {failed}"
"commandpack.compat_header" = "§ePack '{pack}': Inhalte in Minecraft {version} nicht verfügbar"
"commandpack.compat_none" = "§aPack '{pack}' verwendet keine Inhalte, die in Minecraft {version} fehlen."
"commandpack.compat_remedy" = "§7Aktualisiere den Server auf ein neueres Minecraft, um diese Inhalte wiederherzustellen, oder deklariere Ersatzwerte (Dimension blockFallbacks, Block backup)."
"commandpack.update_world_requires_persistent_world" = "§cWähle eine geladene Iris-Welt. Studio-Welten können keine Generierungsaktualisierungen erhalten."
"commandpack.update_world_staged" = "§a{pack} v{version} für Welt '{world}' vorgemerkt. Ein sauberer Neustart aktiviert das Pack mit Geländeübergängen."
"commandpack.update_world_unchanged" = "§aWelt '{world}' verwendet bereits {pack} v{version} mit denselben Generierungseinstellungen. Kein Neustart erforderlich."
[iris.runtime.studio]
opening = "§6Studio §bÖFFNET"
@@ -1688,7 +1692,7 @@ chunk_failed = "Chunk {x},{z} FEHLGESCHLAGEN: {type}"
"progress.indeterminate" = "{bar}§7 §b{phase}§8 | §f{transferred}§8 | §b{rate}/s"
"progress.detail" = "§8 - §7{detail}"
"progress.complete" = "§aIris-Pack '{pack}' installiert§8 | §f{transferred}§7 in §f{elapsed}"
"progress.unchanged" = "§eIris-Pack '{pack}' ist bereits installiert."
"progress.unchanged" = "§eIris-Pack '{pack}' ist bereits installiert. Verwende overwrite=true, um es zu sichern und zu ersetzen."
"progress.failed" = "§cDownload des Iris-Packs fehlgeschlagen.§7 Prüfe die Download-Details oben und versuche es erneut."
"progress.failed_detail" = "§cDownload des Iris-Packs fehlgeschlagen.§7 {error}"
"progress.cancelled" = "§eDownload des Iris-Packs vor der Veröffentlichung abgebrochen."
@@ -1714,14 +1718,17 @@ check_github = "Prüfe, ob die Datei auf GitHub vorhanden ist, und melde das Pro
invalid_dimension = "Ungültiger Dimensionsordner unter dimensions/."
importing = "{name} ({key}) wird importiert"
dimension_key_conflict = "Eine andere Dimension im Pack-Ordner verwendet bereits den Schlüssel {key}. Import fehlgeschlagen!"
pack_key_conflict = "Ein anderer Pack verwendet bereits den Schlüssel {key}. Import fehlgeschlagen!"
"pack_key_conflict" = "Iris-Pack '{key}' ist bereits installiert. Verwende overwrite=true, um es zu sichern und zu ersetzen."
acquired = "{name} erfolgreich abgerufen."
already_installed = "Pack {key} ist bereits installiert, Download wird übersprungen."
"already_installed" = "Pack {key} ist bereits installiert, Download wird übersprungen. Verwende overwrite=true, um es zu sichern und zu ersetzen."
in_progress = "Ein anderer Iris-Pack-Download läuft bereits. Warte, bis er abgeschlossen ist, bevor du es erneut versuchst."
validation_failed = "Pack '{pack}' hat die Validierung nicht bestanden; Welt- und Studio-Erstellung werden verweigert. Gründe:"
validation_reason = " - {reason}"
validated_with_warnings = { other = "Pack '{pack}' mit {count} Warnungen validiert.", one = "Pack '{pack}' mit {count} Warnung validiert." }
validated = "Pack '{pack}' validiert."
"close_studio" = "Schließe das aktive Studio-Projekt, bevor du ein installiertes Pack ersetzt."
"backup_retained" = "Vorheriges Pack unter {path} gesichert."
[iris.client.vision]
title = "Iris Vision"
+10 -3
View File
@@ -1577,6 +1577,10 @@ failed_fragment = " fallidos {failed}"
"commandpack.compat_header" = "§ePack '{pack}': contenido no disponible en Minecraft {version}"
"commandpack.compat_none" = "§aEl pack '{pack}' no usa contenido que falte en Minecraft {version}."
"commandpack.compat_remedy" = "§7Actualiza el servidor a un Minecraft más reciente para recuperar este contenido, o declara alternativas (blockFallbacks de la dimensión, backup del bloque)."
"commandpack.update_world_requires_persistent_world" = "§cElige un mundo de Iris cargado. Los mundos de Studio no pueden recibir actualizaciones de generación."
"commandpack.update_world_staged" = "§a{pack} v{version} preparado para el mundo '{world}'. Un reinicio limpio activará el pack con transición de terreno."
"commandpack.update_world_unchanged" = "§aEl mundo '{world}' ya usa {pack} v{version} con los mismos ajustes de generación. No hace falta reiniciar."
[iris.runtime.studio]
opening = "§6Studio §bABRIENDO"
@@ -1688,7 +1692,7 @@ chunk_failed = "Chunk {x},{z} FALLÓ: {type}"
"progress.indeterminate" = "{bar}§7 §b{phase}§8 | §f{transferred}§8 | §b{rate}/s"
"progress.detail" = "§8 - §7{detail}"
"progress.complete" = "§aEl pack de Iris '{pack}' está instalado§8 | §f{transferred}§7 en §f{elapsed}"
"progress.unchanged" = "§eEl pack de Iris '{pack}' ya está instalado."
"progress.unchanged" = "§eEl pack de Iris '{pack}' ya está instalado. Usa overwrite=true para crear una copia de seguridad y reemplazarlo."
"progress.failed" = "§cLa descarga del pack de Iris ha fallado.§7 Revisa los detalles de la descarga de arriba y vuelve a intentarlo."
"progress.failed_detail" = "§cLa descarga del pack de Iris ha fallado.§7 {error}"
"progress.cancelled" = "§eLa descarga del pack de Iris se canceló antes de publicarlo."
@@ -1714,14 +1718,17 @@ check_github = "¡Comprueba que esté presente en GitHub e informa de ello al pe
invalid_dimension = "Carpeta de dimensión no válida en dimensions/."
importing = "Importando {name} ({key})"
dimension_key_conflict = "Otra dimensión de la carpeta packs ya usa la clave {key}. ¡La importación falló!"
pack_key_conflict = "Otro pack usa la clave {key}. ¡La importación falló!"
"pack_key_conflict" = "El pack de Iris '{key}' ya está instalado. Usa overwrite=true para crear una copia de seguridad y reemplazarlo."
acquired = "{name} se obtuvo correctamente."
already_installed = "El pack {key} ya está instalado, se omite la descarga."
"already_installed" = "El pack {key} ya está instalado, se omite la descarga. Usa overwrite=true para crear una copia de seguridad y reemplazarlo."
in_progress = "Ya hay otra descarga de un pack de Iris en curso. Espera a que termine antes de volver a intentarlo."
validation_failed = "El pack '{pack}' no superó la validación; se rechazará la creación de mundos y de Studio. Motivos:"
validation_reason = " - {reason}"
validated_with_warnings = { one = "El pack '{pack}' se validó con {count} advertencia.", other = "El pack '{pack}' se validó con {count} advertencias." }
validated = "El pack '{pack}' se validó."
"close_studio" = "Cierra el proyecto de Studio activo antes de reemplazar un pack instalado."
"backup_retained" = "Pack anterior guardado en {path}."
[iris.client.vision]
title = "Iris Vision"
+10 -3
View File
@@ -1582,6 +1582,10 @@ failed_fragment = " epäonnistui {failed}"
"commandpack.compat_header" = "§ePaketti '{pack}': sisältö ei ole käytettävissä Minecraft {version} -versiossa"
"commandpack.compat_none" = "§aPaketti '{pack}' ei käytä sisältöä, joka puuttuisi Minecraft {version} -versiosta."
"commandpack.compat_remedy" = "§7Päivitä palvelin uudempaan Minecraft-versioon palauttaaksesi tämän sisällön tai määritä varasisältö (ulottuvuuden blockFallbacks, lohkon backup)."
"commandpack.update_world_requires_persistent_world" = "§cValitse ladattu Iris-maailma. Studio-maailmat eivät voi vastaanottaa generointipäivityksiä."
"commandpack.update_world_staged" = "§a{pack} v{version} valmisteltu maailmalle '{world}'. Hallittu uudelleenkäynnistys ottaa paketin ja maaston häivytyksen käyttöön."
"commandpack.update_world_unchanged" = "§aMaailma '{world}' käyttää jo pakettia {pack} v{version} samoilla generointiasetuksilla. Uudelleenkäynnistystä ei tarvita."
[iris.runtime.studio]
opening = "§6Studio §bAVATAAN"
@@ -1693,7 +1697,7 @@ chunk_failed = "Liha {x},{z} epäonnistui: {type}"
"progress.indeterminate" = "{bar}§7 §b{phase}§8 | §f{transferred}§8 | §b{rate}/s"
"progress.detail" = "§8 - §7{detail}"
"progress.complete" = "§aIris-paketti '{pack}' asennettu§8 | §f{transferred}§7 ajassa §f{elapsed}"
"progress.unchanged" = "§eIris-paketti '{pack}' on jo asennettu."
"progress.unchanged" = "§eIris-paketti '{pack}' on jo asennettu. Varmuuskopioi ja korvaa se käyttämällä overwrite=true."
"progress.failed" = "§cIris-paketin lataus epäonnistui.§7 Tarkista yllä olevat lataustiedot ja yritä uudelleen."
"progress.failed_detail" = "§cIris-paketin lataus epäonnistui.§7 {error}"
"progress.cancelled" = "§eIris-paketin lataus peruutettiin ennen julkaisua."
@@ -1719,14 +1723,17 @@ check_github = "Tarkista, että se on läsnä GitHub Ja raportoikaa tästä henk
invalid_dimension = "Virheellinen ulottuvuuskansio alla dimensions/."
importing = "Tuo {name} ({key})"
dimension_key_conflict = "Toinen ulottuvuus pakkauskansiossa jo käyttää avainta {key}. Tuonti epäonnistui!"
pack_key_conflict = "Toinen pakkaus käyttää avainta {key}. Tuonti epäonnistui!"
"pack_key_conflict" = "Iris-paketti '{key}' on jo asennettu. Varmuuskopioi ja korvaa se käyttämällä overwrite=true."
acquired = "Onnistunut hankinta {name}."
already_installed = "Pack {key} on jo asennettu, lataus ohitetaan."
"already_installed" = "Pack {key} on jo asennettu, lataus ohitetaan. Varmuuskopioi ja korvaa se käyttämällä overwrite=true."
in_progress = "Toinen Iris-paketin lataus on jo käynnissä. Odota sen valmistumista ennen kuin yrität uudelleen."
validation_failed = "Pakkaus{pack}' Epäonnistunut validointi; maailma ja Studio luominen hylätään. Perusteet:"
validation_reason = " - {reason}"
validated_with_warnings = { other = "Pakkaus{pack}\" validoitu {count} varoitukset.", one = "Pakkaus{pack}\" validoitu {count} Varoitus." }
validated = "Pakkaus{pack}' validoitu."
"close_studio" = "Sulje aktiivinen Studio-projekti ennen asennetun paketin korvaamista."
"backup_retained" = "Edellinen paketti tallennettu polkuun {path}."
[iris.client.vision]
title = "Iris Näkö"
+10 -3
View File
@@ -1577,6 +1577,10 @@ failed_fragment = " échecs {failed}"
"commandpack.compat_header" = "§ePack '{pack}' : contenu indisponible sur Minecraft {version}"
"commandpack.compat_none" = "§aLe pack '{pack}' n'utilise aucun contenu absent de Minecraft {version}."
"commandpack.compat_remedy" = "§7Mettez le serveur à jour vers un Minecraft plus récent pour restaurer ce contenu, ou déclarez des replis (blockFallbacks de la dimension, backup du bloc)."
"commandpack.update_world_requires_persistent_world" = "§cChoisissez un monde Iris chargé. Les mondes Studio ne peuvent pas recevoir de mises à jour de génération."
"commandpack.update_world_staged" = "§a{pack} v{version} préparé pour le monde '{world}'. Un redémarrage propre activera le pack avec une transition du terrain."
"commandpack.update_world_unchanged" = "§aLe monde '{world}' utilise déjà {pack} v{version} avec les mêmes paramètres de génération. Aucun redémarrage nécessaire."
[iris.runtime.studio]
opening = "§6Studio §bOUVERTURE"
@@ -1688,7 +1692,7 @@ chunk_failed = "Chunk {x},{z} ÉCHEC : {type}"
"progress.indeterminate" = "{bar}§7 §b{phase}§8 | §f{transferred}§8 | §b{rate}/s"
"progress.detail" = "§8 - §7{detail}"
"progress.complete" = "§aLe pack Iris '{pack}' est installé§8 | §f{transferred}§7 en §f{elapsed}"
"progress.unchanged" = "§eLe pack Iris '{pack}' est déjà installé."
"progress.unchanged" = "§eLe pack Iris '{pack}' est déjà installé. Utilisez overwrite=true pour le sauvegarder et le remplacer."
"progress.failed" = "§cÉchec du téléchargement du pack Iris.§7 Consultez les détails du téléchargement ci-dessus et réessayez."
"progress.failed_detail" = "§cÉchec du téléchargement du pack Iris.§7 {error}"
"progress.cancelled" = "§eTéléchargement du pack Iris annulé avant sa publication."
@@ -1714,14 +1718,17 @@ check_github = "Vérifiez qu'il est présent sur GitHub et signalez-le à l'équ
invalid_dimension = "Dossier de dimension non valide dans dimensions/."
importing = "Importation de {name} ({key})"
dimension_key_conflict = "Une autre dimension du dossier packs utilise déjà la clé {key}. Échec de l'importation !"
pack_key_conflict = "Un autre pack utilise la clé {key}. Échec de l'importation !"
"pack_key_conflict" = "Le pack Iris '{key}' est déjà installé. Utilisez overwrite=true pour le sauvegarder et le remplacer."
acquired = "{name} obtenu avec succès."
already_installed = "Le pack {key} est déjà installé, téléchargement ignoré."
"already_installed" = "Le pack {key} est déjà installé, téléchargement ignoré. Utilisez overwrite=true pour le sauvegarder et le remplacer."
in_progress = "Un autre téléchargement de pack Iris est déjà en cours. Attendez qu’il se termine avant de réessayer."
validation_failed = "Le pack '{pack}' a échoué à la validation ; la création de mondes et de Studio sera refusée. Raisons :"
validation_reason = " - {reason}"
validated_with_warnings = { one = "Le pack '{pack}' a été validé avec {count} avertissement.", other = "Le pack '{pack}' a été validé avec {count} avertissements." }
validated = "Le pack '{pack}' a été validé."
"close_studio" = "Fermez le projet Studio actif avant de remplacer un pack installé."
"backup_retained" = "Ancien pack sauvegardé dans {path}."
[iris.client.vision]
title = "Iris Vision"
+10 -3
View File
@@ -1582,6 +1582,10 @@ failed_fragment = " נכשל {failed}"
"commandpack.compat_header" = "§eחבילה '{pack}': תוכן לא זמין ב-Minecraft {version}"
"commandpack.compat_none" = "§aהחבילה '{pack}' אינה משתמשת בתוכן שחסר ב-Minecraft {version}."
"commandpack.compat_remedy" = "§7עדכן את השרת ל-Minecraft חדש יותר כדי לשחזר את התוכן הזה, או הגדר חלופות (blockFallbacks של הממד, backup של הבלוק)."
"commandpack.update_world_requires_persistent_world" = "§cיש לבחור עולם Iris טעון. עולמות Studio אינם יכולים לקבל עדכוני יצירת עולם."
"commandpack.update_world_staged" = "§a{pack} v{version} הוכן עבור העולם '{world}'. הפעלה מחדש מסודרת תפעיל את החבילה עם שילוב פני השטח."
"commandpack.update_world_unchanged" = "§aהעולם '{world}' כבר משתמש בחבילה {pack} v{version} עם אותן הגדרות יצירה. אין צורך בהפעלה מחדש."
[iris.runtime.studio]
opening = "§6Studio §bנפתח"
@@ -1693,7 +1697,7 @@ chunk_failed = "צ'אנק {x},{z} נכשל: {type}"
"progress.indeterminate" = "{bar}§7 §b{phase}§8 | §f{transferred}§8 | §b{rate}/s"
"progress.detail" = "§8 - §7{detail}"
"progress.complete" = "§aחבילת Iris '{pack}' הותקנה§8 | §f{transferred}§7 בתוך §f{elapsed}"
"progress.unchanged" = "§eחבילת Iris '{pack}' כבר מותקנת."
"progress.unchanged" = "§eחבילת Iris '{pack}' כבר מותקנת. יש להשתמש ב־overwrite=true כדי לגבות ולהחליף אותה."
"progress.failed" = "§cהורדת חבילת Iris נכשלה.§7 יש לעיין בפרטי ההורדה שלעיל ולנסות שוב."
"progress.failed_detail" = "§cהורדת חבילת Iris נכשלה.§7 {error}"
"progress.cancelled" = "§eהורדת חבילת Iris בוטלה לפני הפרסום."
@@ -1719,14 +1723,17 @@ check_github = "בדוק כי הוא נוכח GitHub לדווח על זה לצו
invalid_dimension = "תיקיית מימד לא חוקית תחת dimensions/."
importing = "יבוא {name} ({key})"
dimension_key_conflict = "מימד נוסף בתיקיה של הלהקה כבר משתמש המפתח {key}. ייבוא נכשל!"
pack_key_conflict = "חבילה נוספת משתמשת במפתח {key}. ייבוא נכשל!"
"pack_key_conflict" = "חבילת Iris '{key}' כבר מותקנת. יש להשתמש ב־overwrite=true כדי לגבות ולהחליף אותה."
acquired = "נרכשה בהצלחה {name}."
already_installed = "החבילה {key} כבר מותקנת, ההורדה מדולגת."
"already_installed" = "החבילה {key} כבר מותקנת, ההורדה מדולגת. יש להשתמש ב־overwrite=true כדי לגבות ולהחליף אותה."
in_progress = "הורדה אחרת של חבילת Iris כבר מתבצעת. יש להמתין לסיומה לפני ניסיון נוסף."
validation_failed = "Pack »{pack}\"התאימות הכושל; יצירת העולם והסטודיו לא תסרב. סיבות:"
validation_reason = " - {reason}"
validated_with_warnings = { other = "Pack »{pack}\"תאמת עם {count} אזהרות", one = "Pack »{pack}\"תאמת עם {count} אזהרה." }
validated = "Pack »{pack}\"מאומת."
"close_studio" = "יש לסגור את פרויקט Studio הפעיל לפני החלפת חבילה מותקנת."
"backup_retained" = "החבילה הקודמת נשמרה בנתיב {path}."
[iris.client.vision]
title = "Iris חזון"
+10 -3
View File
@@ -1577,6 +1577,10 @@ failed_fragment = " non riusciti {failed}"
"commandpack.compat_header" = "§ePack '{pack}': contenuti non disponibili su Minecraft {version}"
"commandpack.compat_none" = "§aIl pack '{pack}' non usa contenuti assenti da Minecraft {version}."
"commandpack.compat_remedy" = "§7Aggiorna il server a un Minecraft più recente per ripristinare questi contenuti, oppure dichiara delle alternative (blockFallbacks della dimensione, backup del blocco)."
"commandpack.update_world_requires_persistent_world" = "§cScegli un mondo Iris caricato. I mondi Studio non possono ricevere aggiornamenti della generazione."
"commandpack.update_world_staged" = "§a{pack} v{version} preparato per il mondo '{world}'. Un riavvio regolare attiverà il pacchetto con raccordo del terreno."
"commandpack.update_world_unchanged" = "§aIl mondo '{world}' usa già {pack} v{version} con le stesse impostazioni di generazione. Non serve riavviare."
[iris.runtime.studio]
opening = "§6Studio §bAPERTURA"
@@ -1688,7 +1692,7 @@ chunk_failed = "Chunk {x},{z} NON RIUSCITO: {type}"
"progress.indeterminate" = "{bar}§7 §b{phase}§8 | §f{transferred}§8 | §b{rate}/s"
"progress.detail" = "§8 - §7{detail}"
"progress.complete" = "§aPack Iris '{pack}' installato§8 | §f{transferred}§7 in §f{elapsed}"
"progress.unchanged" = "§eIl pack Iris '{pack}' è già installato."
"progress.unchanged" = "§eIl pack Iris '{pack}' è già installato. Usa overwrite=true per crearne una copia di sicurezza e sostituirlo."
"progress.failed" = "§cDownload del pack Iris non riuscito.§7 Controlla i dettagli del download qui sopra e riprova."
"progress.failed_detail" = "§cDownload del pack Iris non riuscito.§7 {error}"
"progress.cancelled" = "§eDownload del pack Iris annullato prima della pubblicazione."
@@ -1714,14 +1718,17 @@ check_github = "Verifica che sia presente su GitHub e segnala il problema allo s
invalid_dimension = "Cartella di dimensione non valida in dimensions/."
importing = "Importazione {name} ({key})"
dimension_key_conflict = "Un'altra dimensione nella cartella dei Pack usa già la chiave {key}. Importazione non riuscita!"
pack_key_conflict = "Un altro Pack usa già la chiave {key}. Importazione non riuscita!"
"pack_key_conflict" = "Il pack Iris '{key}' è già installato. Usa overwrite=true per crearne una copia di sicurezza e sostituirlo."
acquired = "{name} acquisito correttamente."
already_installed = "Il pack {key} è già installato, download saltato."
"already_installed" = "Il pack {key} è già installato, download saltato. Usa overwrite=true per crearne una copia di sicurezza e sostituirlo."
in_progress = "È già in corso il download di un altro pack Iris. Attendi che termini prima di riprovare."
validation_failed = "Il Pack '{pack}' non ha superato la convalida; la creazione di mondi e Studio verrà rifiutata. Motivi:"
validation_reason = " - {reason}"
validated_with_warnings = { other = "Pack '{pack}' convalidato con {count} avvisi.", one = "Pack '{pack}' convalidato con {count} avviso." }
validated = "Pack '{pack}' convalidato."
"close_studio" = "Chiudi il progetto Studio attivo prima di sostituire un pacchetto installato."
"backup_retained" = "Pacchetto precedente salvato in {path}."
[iris.client.vision]
title = "Iris Vision"
+10 -3
View File
@@ -1580,6 +1580,10 @@ failed_fragment = " 失敗 {failed}"
"commandpack.compat_header" = "§eパック '{pack}': Minecraft {version} では利用できない内容があります"
"commandpack.compat_none" = "§aパック '{pack}' に Minecraft {version} で欠けている内容はありません。"
"commandpack.compat_remedy" = "§7この内容を復元するにはサーバーを新しい Minecraft に更新するか、代替を宣言してください (ディメンションの blockFallbacks、ブロックの backup)。"
"commandpack.update_world_requires_persistent_world" = "§c読み込み済みの Iris ワールドを選択してください。Studio ワールドには生成の更新を適用できません。"
"commandpack.update_world_staged" = "§aワールド '{world}' 用に {pack} v{version} を準備しました。正常な再起動後、地形をなじませながら新しいパックが有効になります。"
"commandpack.update_world_unchanged" = "§aワールド '{world}' は同じ生成設定の {pack} v{version} を使用しています。再起動は不要です。"
[iris.runtime.studio]
opening = "§6Studio §bオープン中"
@@ -1691,7 +1695,7 @@ chunk_failed = "チャンク {x},{z} 失敗: {type}"
"progress.indeterminate" = "{bar}§7 §b{phase}§8 | §f{transferred}§8 | §b{rate}/s"
"progress.detail" = "§8 - §7{detail}"
"progress.complete" = "§aIris パック '{pack}' をインストールしました§8 | §f{transferred}§7、所要時間 §f{elapsed}"
"progress.unchanged" = "§eIris パック '{pack}' はすでにインストールされています。"
"progress.unchanged" = "§eIris パック '{pack}' はすでにインストールされています。 バックアップして置き換えるには overwrite=true を指定してください。"
"progress.failed" = "§cIris パックのダウンロードに失敗しました。§7 上記のダウンロード詳細を確認して、もう一度お試しください。"
"progress.failed_detail" = "§cIris パックのダウンロードに失敗しました。§7 {error}"
"progress.cancelled" = "§eIris パックのダウンロードは公開前にキャンセルされました。"
@@ -1717,14 +1721,17 @@ check_github = "GitHub に存在することを確認し、スタッフへ報告
invalid_dimension = "dimensions/ 内のディメンションフォルダーが無効です。"
importing = "{name}({key})をインポートしています"
dimension_key_conflict = "packs フォルダー内の別のディメンションがキー {key} をすでに使用しています。インポートに失敗しました!"
pack_key_conflict = "別のパックがキー {key} を使用しています。インポートに失敗しました!"
"pack_key_conflict" = "Iris パック '{key}' はすでにインストールされています。 バックアップして置き換えるには overwrite=true を指定してください。"
acquired = "{name} を取得しました。"
already_installed = "パック {key} は既にインストールされているため、ダウンロードをスキップします。"
"already_installed" = "パック {key} は既にインストールされているため、ダウンロードをスキップします。 バックアップして置き換えるには overwrite=true を指定してください。"
in_progress = "別の Iris パックのダウンロードがすでに進行中です。完了してからもう一度お試しください。"
validation_failed = "パック '{pack}' は検証に失敗しました。ワールドと Studio の作成を拒否します。理由:"
validation_reason = " - {reason}"
validated_with_warnings = { other = "パック '{pack}' の検証が {count} 件の警告付きで完了しました。", one = "パック '{pack}' の検証が {count} 件の警告付きで完了しました。" }
validated = "パック '{pack}' の検証が完了しました。"
"close_studio" = "インストール済みのパックを置き換える前に、開いている Studio プロジェクトを閉じてください。"
"backup_retained" = "以前のパックを {path} に保存しました。"
[iris.client.vision]
title = "Iris ビジョン"
+10 -3
View File
@@ -1580,6 +1580,10 @@ failed_fragment = " 실패한 {failed}"
"commandpack.compat_header" = "§e팩 '{pack}': Minecraft {version} 에서 사용할 수 없는 콘텐츠"
"commandpack.compat_none" = "§a팩 '{pack}' 은 Minecraft {version} 에 없는 콘텐츠를 사용하지 않습니다."
"commandpack.compat_remedy" = "§7이 콘텐츠를 복원하려면 서버를 최신 Minecraft 로 업데이트하거나 대체를 선언하세요 (차원의 blockFallbacks, 블록의 backup)."
"commandpack.update_world_requires_persistent_world" = "§c로드된 Iris 월드를 선택하세요. Studio 월드는 생성 업데이트를 받을 수 없습니다."
"commandpack.update_world_staged" = "§a월드 '{world}'에 사용할 {pack} v{version}을 준비했습니다. 정상적으로 재시작하면 지형 혼합과 함께 팩이 활성화됩니다."
"commandpack.update_world_unchanged" = "§a월드 '{world}'는 이미 같은 생성 설정으로 {pack} v{version}을 사용 중입니다. 재시작할 필요가 없습니다."
[iris.runtime.studio]
opening = "§6Studio §b여는 중"
@@ -1691,7 +1695,7 @@ chunk_failed = "주 메뉴 {x},{z} 실패: {type}"
"progress.indeterminate" = "{bar}§7 §b{phase}§8 | §f{transferred}§8 | §b{rate}/s"
"progress.detail" = "§8 - §7{detail}"
"progress.complete" = "§aIris 팩 '{pack}' 설치 완료§8 | §f{transferred}§7, 소요 시간 §f{elapsed}"
"progress.unchanged" = "§eIris 팩 '{pack}'은(는) 이미 설치되어 있습니다."
"progress.unchanged" = "§eIris 팩 '{pack}'은(는) 이미 설치되어 있습니다. 백업하고 교체하려면 overwrite=true를 사용하세요."
"progress.failed" = "§cIris 팩 다운로드에 실패했습니다.§7 위의 다운로드 세부 정보를 확인한 후 다시 시도하세요."
"progress.failed_detail" = "§cIris 팩 다운로드에 실패했습니다.§7 {error}"
"progress.cancelled" = "§eIris 팩 다운로드가 게시 전에 취소되었습니다."
@@ -1717,14 +1721,17 @@ check_github = "GitHub에 있는지 확인하고 직원에게 보고하세요!"
invalid_dimension = "잘못된 차원 폴더 dimensions/."
importing = "관련 상품 {name} ({key})"
dimension_key_conflict = "packs 폴더의 다른 차원이 이미 키 {key}을(를) 사용합니다. 가져오기에 실패했습니다!"
pack_key_conflict = "다른 팩이 키 {key}을(를) 사용합니다. 가져오기에 실패했습니다!"
"pack_key_conflict" = "Iris 팩 '{key}'은(는) 이미 설치되어 있습니다. 백업하고 교체하려면 overwrite=true를 사용하세요."
acquired = "성공적으로 취득 {name}."
already_installed = "팩 {key}이(가) 이미 설치되어 있어 다운로드를 건너뜁니다."
"already_installed" = "팩 {key}이(가) 이미 설치되어 있어 다운로드를 건너뜁니다. 백업하고 교체하려면 overwrite=true를 사용하세요."
in_progress = "다른 Iris 팩 다운로드가 이미 진행 중입니다. 완료될 때까지 기다린 후 다시 시도하세요."
validation_failed = "팩 '{pack}' 유효성 검사; 세계 및 스튜디오 생성은 거부됩니다. 이유:"
validation_reason = " - {reason}"
validated_with_warnings = { other = "팩 '{pack}' 유효성 {count} 경고.", one = "팩 '{pack}' 유효성 {count} 경고." }
validated = "팩 '{pack}' 유효성."
"close_studio" = "설치된 팩을 교체하기 전에 활성 Studio 프로젝트를 닫으세요."
"backup_retained" = "이전 팩을 {path}에 저장했습니다."
[iris.client.vision]
title = "Iris Vision"
+10 -3
View File
@@ -1582,6 +1582,10 @@ failed_fragment = " nepavyko {failed}"
"commandpack.compat_header" = "§ePaketas '{pack}': turinys nepasiekiamas Minecraft {version}"
"commandpack.compat_none" = "§aPaketas '{pack}' nenaudoja turinio, kurio trūksta Minecraft {version}."
"commandpack.compat_remedy" = "§7Atnaujinkite serverį į naujesnį Minecraft, kad atkurtumėte šį turinį, arba paskelbkite atsargas (dimensijos blockFallbacks, bloko backup)."
"commandpack.update_world_requires_persistent_world" = "§cPasirinkite įkeltą Iris pasaulį. Studio pasauliams negalima taikyti generavimo atnaujinimų."
"commandpack.update_world_staged" = "§aPasauliui '{world}' paruoštas {pack} v{version}. Tvarkingai paleidus iš naujo, paketas bus aktyvuotas su reljefo perėjimais."
"commandpack.update_world_unchanged" = "§aPasaulis '{world}' jau naudoja {pack} v{version} su tais pačiais generavimo nustatymais. Paleisti iš naujo nereikia."
[iris.runtime.studio]
opening = "§6Studio §bATIDAROMA"
@@ -1693,7 +1697,7 @@ chunk_failed = "Šriftas {x},{z} nepavyko: {type}"
"progress.indeterminate" = "{bar}§7 §b{phase}§8 | §f{transferred}§8 | §b{rate}/s"
"progress.detail" = "§8 - §7{detail}"
"progress.complete" = "§a„Iris“ paketas '{pack}' įdiegtas§8 | §f{transferred}§7 per §f{elapsed}"
"progress.unchanged" = "§e„Iris“ paketas '{pack}' jau įdiegtas."
"progress.unchanged" = "§e„Iris“ paketas '{pack}' jau įdiegtas. Naudokite overwrite=true, kad sukurtumėte atsarginę kopiją ir jį pakeistumėte."
"progress.failed" = "§cNepavyko atsisiųsti „Iris“ paketo.§7 Peržiūrėkite aukščiau pateiktą atsisiuntimo informaciją ir bandykite dar kartą."
"progress.failed_detail" = "§cNepavyko atsisiųsti „Iris“ paketo.§7 {error}"
"progress.cancelled" = "§e„Iris“ paketo atsisiuntimas atšauktas prieš publikavimą."
@@ -1719,14 +1723,17 @@ check_github = "Patikrinkite, ar jis yra GitHub ir pranešti apie tai darbuotoja
invalid_dimension = "Netinkamas dimensijos aplankas pagal dimensions/."
importing = "Importuojama {name} ({key})"
dimension_key_conflict = "Kitas matmuo paketų aplanke jau naudoja raktą {key}. Importuoti nepavyko!"
pack_key_conflict = "Kita pakuotė naudoja raktą {key}. Importuoti nepavyko!"
"pack_key_conflict" = "„Iris“ paketas '{key}' jau įdiegtas. Naudokite overwrite=true, kad sukurtumėte atsarginę kopiją ir jį pakeistumėte."
acquired = "Sėkmingai įgyta {name}."
already_installed = "Paketas {key} jau įdiegtas, atsisiuntimas praleidžiamas."
"already_installed" = "Paketas {key} jau įdiegtas, atsisiuntimas praleidžiamas. Naudokite overwrite=true, kad sukurtumėte atsarginę kopiją ir jį pakeistumėte."
in_progress = "Jau vyksta kitas „Iris“ paketo atsisiuntimas. Palaukite, kol jis bus baigtas, ir bandykite dar kartą."
validation_failed = "Pakuotė \"{pack}\"nepavyko patvirtinimas; pasaulio ir Studio kūrimas bus atsisakyta. Motyvai:"
validation_reason = " - {reason}"
validated_with_warnings = { other = "Pakuotė \"{pack}\"patvirtinta {count} įspėjimai.", one = "Pakuotė \"{pack}\"patvirtinta {count} įspėjimas." }
validated = "Pakuotė \"{pack}\"patvirtintas."
"close_studio" = "Prieš pakeisdami įdiegtą paketą, uždarykite aktyvų Studio projektą."
"backup_retained" = "Ankstesnis paketas išsaugotas čia: {path}."
[iris.client.vision]
title = "Iris Regėjimas"
+10 -3
View File
@@ -1577,6 +1577,10 @@ failed_fragment = " mislukt {failed}"
"commandpack.compat_header" = "§ePack '{pack}': inhoud niet beschikbaar op Minecraft {version}"
"commandpack.compat_none" = "§aPack '{pack}' gebruikt geen inhoud die ontbreekt in Minecraft {version}."
"commandpack.compat_remedy" = "§7Werk de server bij naar een nieuwere Minecraft om deze inhoud te herstellen, of declareer terugvallen (blockFallbacks van de dimensie, backup van het blok)."
"commandpack.update_world_requires_persistent_world" = "§cKies een geladen Iris-wereld. Studio-werelden kunnen geen generatie-updates ontvangen."
"commandpack.update_world_staged" = "§a{pack} v{version} klaargezet voor wereld '{world}'. Een nette herstart activeert het pakket met terreinovergangen."
"commandpack.update_world_unchanged" = "§aWereld '{world}' gebruikt al {pack} v{version} met dezelfde generatie-instellingen. Herstarten is niet nodig."
[iris.runtime.studio]
opening = "§6Studio §bOPENEN"
@@ -1688,7 +1692,7 @@ chunk_failed = "Chunk. {x},{z} mislukt: {type}"
"progress.indeterminate" = "{bar}§7 §b{phase}§8 | §f{transferred}§8 | §b{rate}/s"
"progress.detail" = "§8 - §7{detail}"
"progress.complete" = "§aIris-pack '{pack}' geïnstalleerd§8 | §f{transferred}§7 in §f{elapsed}"
"progress.unchanged" = "§eIris-pack '{pack}' is al geïnstalleerd."
"progress.unchanged" = "§eIris-pack '{pack}' is al geïnstalleerd. Gebruik overwrite=true om een reservekopie te maken en het te vervangen."
"progress.failed" = "§cDownload van het Iris-pack mislukt.§7 Bekijk de downloadgegevens hierboven en probeer het opnieuw."
"progress.failed_detail" = "§cDownload van het Iris-pack mislukt.§7 {error}"
"progress.cancelled" = "§eDownload van het Iris-pack geannuleerd vóór publicatie."
@@ -1714,14 +1718,17 @@ check_github = "Controleer of het aanwezig is op GitHub En meld dit aan het pers
invalid_dimension = "Ongeldige dimensiemap onder dimensions/."
importing = "Importeren {name} ({key})"
dimension_key_conflict = "Een andere dimensie in de packs-map gebruikt al de sleutel {key}. Importeren mislukt!"
pack_key_conflict = "Een ander pakje gebruikt de sleutel {key}. Importeren mislukt!"
"pack_key_conflict" = "Iris-pack '{key}' is al geïnstalleerd. Gebruik overwrite=true om een reservekopie te maken en het te vervangen."
acquired = "Succesvol verworven {name}."
already_installed = "Pack {key} is al geïnstalleerd, download wordt overgeslagen."
"already_installed" = "Pack {key} is al geïnstalleerd, download wordt overgeslagen. Gebruik overwrite=true om een reservekopie te maken en het te vervangen."
in_progress = "Er wordt al een ander Iris-pack gedownload. Wacht tot dit is voltooid voordat je het opnieuw probeert."
validation_failed = "Verpakking{pack}' mislukte validatie; wereld en Studio creatie zal worden geweigerd. Motivering:"
validation_reason = " - {reason}"
validated_with_warnings = { other = "Verpakking{pack}' gevalideerd met {count} waarschuwingen.", one = "Verpakking{pack}' gevalideerd met {count} waarschuwing." }
validated = "Verpakking{pack}' gevalideerd."
"close_studio" = "Sluit het actieve Studio-project voordat je een geïnstalleerd pakket vervangt."
"backup_retained" = "Vorig pakket opgeslagen in {path}."
[iris.client.vision]
title = "Iris Gezicht"
+10 -3
View File
@@ -1582,6 +1582,10 @@ failed_fragment = " nieudany {failed}"
"commandpack.compat_header" = "§ePaczka '{pack}': zawartość niedostępna w Minecraft {version}"
"commandpack.compat_none" = "§aPaczka '{pack}' nie używa zawartości brakującej w Minecraft {version}."
"commandpack.compat_remedy" = "§7Zaktualizuj serwer do nowszego Minecraft, aby przywrócić tę zawartość, albo zadeklaruj zamienniki (blockFallbacks wymiaru, backup bloku)."
"commandpack.update_world_requires_persistent_world" = "§cWybierz wczytany świat Iris. Światy Studio nie mogą otrzymywać aktualizacji generowania."
"commandpack.update_world_staged" = "§aPrzygotowano {pack} v{version} dla świata '{world}'. Prawidłowy restart aktywuje pakiet z przejściami terenu."
"commandpack.update_world_unchanged" = "§aŚwiat '{world}' już używa {pack} v{version} z tymi samymi ustawieniami generowania. Restart nie jest potrzebny."
[iris.runtime.studio]
opening = "§6Studio §bOTWIERANIE"
@@ -1693,7 +1697,7 @@ chunk_failed = "Chunk Przewodniczący {x},{z} niepowodzenie: {type}"
"progress.indeterminate" = "{bar}§7 §b{phase}§8 | §f{transferred}§8 | §b{rate}/s"
"progress.detail" = "§8 - §7{detail}"
"progress.complete" = "§aPakiet Iris '{pack}' zainstalowany§8 | §f{transferred}§7 w §f{elapsed}"
"progress.unchanged" = "§ePakiet Iris '{pack}' jest już zainstalowany."
"progress.unchanged" = "§ePakiet Iris '{pack}' jest już zainstalowany. Użyj overwrite=true, aby utworzyć kopię zapasową i go zastąpić."
"progress.failed" = "§cPobieranie pakietu Iris nie powiodło się.§7 Sprawdź powyższe szczegóły pobierania i spróbuj ponownie."
"progress.failed_detail" = "§cPobieranie pakietu Iris nie powiodło się.§7 {error}"
"progress.cancelled" = "§ePobieranie pakietu Iris anulowano przed publikacją."
@@ -1719,14 +1723,17 @@ check_github = "Sprawdź, czy jest obecny na GitHub i zgłosić do do personelu!
invalid_dimension = "Nieprawidłowy folder wymiarów pod dimensions/."
importing = "Przywóz {name} ({key})"
dimension_key_conflict = "Inny wymiar w folderze pakietów już używa klucza {key}. Import nie powiódł się!"
pack_key_conflict = "Inny pakiet używa klucza {key}. Import nie powiódł się!"
"pack_key_conflict" = "Pakiet Iris '{key}' jest już zainstalowany. Użyj overwrite=true, aby utworzyć kopię zapasową i go zastąpić."
acquired = "Udane nabycie {name}."
already_installed = "Pakiet {key} jest już zainstalowany, pomijanie pobierania."
"already_installed" = "Pakiet {key} jest już zainstalowany, pomijanie pobierania. Użyj overwrite=true, aby utworzyć kopię zapasową i go zastąpić."
in_progress = "Trwa już pobieranie innego pakietu Iris. Poczekaj na jego zakończenie, zanim spróbujesz ponownie."
validation_failed = "Paczka \"{pack}\"nieudaną walidację; świat i tworzenie studia zostaną odrzucone. Uzasadnienie:"
validation_reason = " - {reason}"
validated_with_warnings = { other = "Paczka \"{pack}\"zatwierdzone {count} Ostrzeżenia.", one = "Paczka \"{pack}\"zatwierdzone {count} Ostrzeżenie." }
validated = "Paczka \"{pack}\"zatwierdzone."
"close_studio" = "Zamknij aktywny projekt Studio przed zastąpieniem zainstalowanego pakietu."
"backup_retained" = "Poprzedni pakiet zapisano w {path}."
[iris.client.vision]
title = "Iris Wizja"
+10 -3
View File
@@ -1577,6 +1577,10 @@ failed_fragment = " falhou {failed}"
"commandpack.compat_header" = "§ePack '{pack}': conteúdo indisponível no Minecraft {version}"
"commandpack.compat_none" = "§aO pack '{pack}' não usa conteúdo em falta no Minecraft {version}."
"commandpack.compat_remedy" = "§7Atualize o servidor para um Minecraft mais recente para restaurar este conteúdo, ou declare alternativas (blockFallbacks da dimensão, backup do bloco)."
"commandpack.update_world_requires_persistent_world" = "§cEscolhe um mundo Iris carregado. Os mundos Studio não podem receber atualizações de geração."
"commandpack.update_world_staged" = "§a{pack} v{version} preparado para o mundo '{world}'. Um reinício normal ativará o pacote com transição do terreno."
"commandpack.update_world_unchanged" = "§aO mundo '{world}' já utiliza {pack} v{version} com as mesmas definições de geração. Não é necessário reiniciar."
[iris.runtime.studio]
opening = "§6Studio §bA ABRIR"
@@ -1688,7 +1692,7 @@ chunk_failed = "Chunk. {x},{z} falhou: {type}"
"progress.indeterminate" = "{bar}§7 §b{phase}§8 | §f{transferred}§8 | §b{rate}/s"
"progress.detail" = "§8 - §7{detail}"
"progress.complete" = "§aPack Iris '{pack}' instalado§8 | §f{transferred}§7 em §f{elapsed}"
"progress.unchanged" = "§eO pack Iris '{pack}' já está instalado."
"progress.unchanged" = "§eO pack Iris '{pack}' já está instalado. Usa overwrite=true para criar uma cópia de segurança e substituí-lo."
"progress.failed" = "§cA transferência do pack Iris falhou.§7 Reveja os detalhes da transferência acima e tente novamente."
"progress.failed_detail" = "§cA transferência do pack Iris falhou.§7 {error}"
"progress.cancelled" = "§eA transferência do pack Iris foi cancelada antes da publicação."
@@ -1714,14 +1718,17 @@ check_github = "Verifique se está presente em GitHub E reporta isto ao pessoal!
invalid_dimension = "Pasta de dimensões inválida sob dimensions/."
importing = "Importação {name} ({key})"
dimension_key_conflict = "Outra dimensão na pasta pacotes já está usando a chave {key}. A importação falhou!"
pack_key_conflict = "Outro pacote está usando a chave {key}. A importação falhou!"
"pack_key_conflict" = "O pack Iris '{key}' já está instalado. Usa overwrite=true para criar uma cópia de segurança e substituí-lo."
acquired = "Adquirido com sucesso {name}."
already_installed = "O pack {key} já está instalado, download ignorado."
"already_installed" = "O pack {key} já está instalado, download ignorado. Usa overwrite=true para criar uma cópia de segurança e substituí-lo."
in_progress = "Já está em curso a transferência de outro pack Iris. Aguarde que termine antes de tentar novamente."
validation_failed = "Embalar '{pack}' validação falhada; a criação de mundo e estúdio será recusada. Motivos:"
validation_reason = " - {reason}"
validated_with_warnings = { other = "Embalar '{pack}' validado com {count} avisos.", one = "Embalar '{pack}' validado com {count} Aviso." }
validated = "Embalar '{pack}' validado."
"close_studio" = "Fecha o projeto Studio ativo antes de substituir um pacote instalado."
"backup_retained" = "Pacote anterior guardado em {path}."
[iris.client.vision]
title = "Iris Visão"
+10 -3
View File
@@ -1582,6 +1582,10 @@ failed_fragment = " неудачный {failed}"
"commandpack.compat_header" = "§eПак '{pack}': содержимое недоступно в Minecraft {version}"
"commandpack.compat_none" = "§aПак '{pack}' не использует содержимое, которого нет в Minecraft {version}."
"commandpack.compat_remedy" = "§7Обновите сервер до более новой версии Minecraft, чтобы вернуть это содержимое, или объявите замены (blockFallbacks измерения, backup блока)."
"commandpack.update_world_requires_persistent_world" = "§cВыберите загруженный мир Iris. Миры Studio не могут получать обновления генерации."
"commandpack.update_world_staged" = "§a{pack} v{version} подготовлен для мира '{world}'. Штатный перезапуск активирует пакет с плавным переходом рельефа."
"commandpack.update_world_unchanged" = "§aМир '{world}' уже использует {pack} v{version} с теми же настройками генерации. Перезапуск не требуется."
[iris.runtime.studio]
opening = "§6Studio §bОТКРЫТИЕ"
@@ -1693,7 +1697,7 @@ chunk_failed = "Кусок {x},{z} не удалось: {type}"
"progress.indeterminate" = "{bar}§7 §b{phase}§8 | §f{transferred}§8 | §b{rate}/s"
"progress.detail" = "§8 - §7{detail}"
"progress.complete" = "§aПак Iris «{pack}» установлен§8 | §f{transferred}§7 за §f{elapsed}"
"progress.unchanged" = "§eПак Iris «{pack}» уже установлен."
"progress.unchanged" = "§eПак Iris «{pack}» уже установлен. Используйте overwrite=true, чтобы создать резервную копию и заменить его."
"progress.failed" = "§cНе удалось загрузить пак Iris.§7 Проверьте сведения о загрузке выше и повторите попытку."
"progress.failed_detail" = "§cНе удалось загрузить пак Iris.§7 {error}"
"progress.cancelled" = "§eЗагрузка пака Iris отменена до публикации."
@@ -1719,14 +1723,17 @@ check_github = "Убедитесь, что он присутствует на Gi
invalid_dimension = "Недействительная размерная папка под dimensions/."
importing = "импортировать {name} ({key})"
dimension_key_conflict = "Еще одно измерение в папке пакетов уже использует ключ {key}. Импорт провалился!"
pack_key_conflict = "Другой пакет использует ключ. {key}. Импорт провалился!"
"pack_key_conflict" = "Пак Iris «{key}» уже установлен. Используйте overwrite=true, чтобы создать резервную копию и заменить его."
acquired = "Успешно приобретенный {name}."
already_installed = "Пак {key} уже установлен, загрузка пропущена."
"already_installed" = "Пак {key} уже установлен, загрузка пропущена. Используйте overwrite=true, чтобы создать резервную копию и заменить его."
in_progress = "Уже выполняется загрузка другого пака Iris. Дождитесь её завершения, прежде чем повторить попытку."
validation_failed = "Пакуй.{pack}Неудачная проверка; мир и создание студии будут отклонены. Причины:"
validation_reason = " - {reason}"
validated_with_warnings = { other = "Пакуй.{pack}подтвержденный {count} предупреждения.", one = "Пакуй.{pack}подтвержденный {count} предупреждение." }
validated = "Пакуй.{pack}Подтверждено."
"close_studio" = "Закройте активный проект Studio перед заменой установленного пакета."
"backup_retained" = "Предыдущий пакет сохранён в {path}."
[iris.client.vision]
title = "Iris Видение"
+10 -3
View File
@@ -1582,6 +1582,10 @@ failed_fragment = " Başarısız oldu {failed}"
"commandpack.compat_header" = "§ePaket '{pack}': içerik Minecraft {version} sürümünde yok"
"commandpack.compat_none" = "§aPaket '{pack}' Minecraft {version} sürümünde eksik olan içerik kullanmıyor."
"commandpack.compat_remedy" = "§7Bu içeriği geri kazanmak için sunucuyu daha yeni bir Minecraft sürümüne güncelleyin veya yedekler tanımlayın (boyutun blockFallbacks, bloğun backup)."
"commandpack.update_world_requires_persistent_world" = "§cYüklü bir Iris dünyası seçin. Studio dünyaları üretim güncellemeleri alamaz."
"commandpack.update_world_staged" = "§a'{world}' dünyası için {pack} v{version} hazırlandı. Düzgün bir yeniden başlatma, paketi arazi geçişleriyle etkinleştirir."
"commandpack.update_world_unchanged" = "§a'{world}' dünyası aynı üretim ayarlarıyla zaten {pack} v{version} kullanıyor. Yeniden başlatma gerekmiyor."
[iris.runtime.studio]
opening = "§6Studio §bAÇILIYOR"
@@ -1693,7 +1697,7 @@ chunk_failed = "Chunk. {x},{z} başarısız: {type}"
"progress.indeterminate" = "{bar}§7 §b{phase}§8 | §f{transferred}§8 | §b{rate}/s"
"progress.detail" = "§8 - §7{detail}"
"progress.complete" = "§aIris paketi '{pack}' kuruldu§8 | §f{transferred}§7, süre: §f{elapsed}"
"progress.unchanged" = "§eIris paketi '{pack}' zaten kurulu."
"progress.unchanged" = "§eIris paketi '{pack}' zaten kurulu. Yedeklemek ve değiştirmek için overwrite=true kullanın."
"progress.failed" = "§cIris paketi indirilemedi.§7 Yukarıdaki indirme ayrıntılarını inceleyip yeniden deneyin."
"progress.failed_detail" = "§cIris paketi indirilemedi.§7 {error}"
"progress.cancelled" = "§eIris paketi indirme işlemi yayınlanmadan önce iptal edildi."
@@ -1719,14 +1723,17 @@ check_github = "GitHub'de mevcut olduğunu kontrol edin ve bunu personele rapor
invalid_dimension = "geçersiz boyut klasörü altında dimensions/."
importing = "İthalat {name} ({key})"
dimension_key_conflict = "Paketlerdeki başka bir boyut zaten anahtarı kullanıyor {key}. İthalat başarısız oldu!"
pack_key_conflict = "Başka bir paket anahtarı kullanıyor {key}. İthalat başarısız oldu!"
"pack_key_conflict" = "Iris paketi '{key}' zaten kurulu. Yedeklemek ve değiştirmek için overwrite=true kullanın."
acquired = "Başarılı bir şekilde satın alındı {name}."
already_installed = "{key} paketi zaten kurulu, indirme atlanıyor."
"already_installed" = "{key} paketi zaten kurulu, indirme atlanıyor. Yedeklemek ve değiştirmek için overwrite=true kullanın."
in_progress = "Başka bir Iris paketi indirme işlemi zaten devam ediyor. Yeniden denemeden önce tamamlanmasını bekleyin."
validation_failed = "Pack \"{pack}“Başarısız doğrulama; dünya ve Stüdyo yaratımı reddedilecektir. Sebepler:"
validation_reason = " - {reason}"
validated_with_warnings = { other = "Pack \"{pack}\" ile doğrulandı\" {count} Uyarılar.", one = "Pack \"{pack}\" ile doğrulandı\" {count} Uyarı." }
validated = "Pack \"{pack}\"Doğrulandı."
"close_studio" = "Yüklü bir paketi değiştirmeden önce etkin Studio projesini kapatın."
"backup_retained" = "Önceki paket {path} konumuna kaydedildi."
[iris.client.vision]
title = "Iris Vizyon"
+10 -3
View File
@@ -1580,6 +1580,10 @@ failed_fragment = " bị lỗi {failed}"
"commandpack.compat_header" = "§eGói '{pack}': nội dung không có trên Minecraft {version}"
"commandpack.compat_none" = "§aGói '{pack}' không dùng nội dung nào thiếu trên Minecraft {version}."
"commandpack.compat_remedy" = "§7Cập nhật máy chủ lên Minecraft mới hơn để khôi phục nội dung này, hoặc khai báo phương án dự phòng (blockFallbacks của chiều, backup của khối)."
"commandpack.update_world_requires_persistent_world" = "§cChọn một thế giới Iris đã được tải. Thế giới Studio không thể nhận bản cập nhật tạo địa hình."
"commandpack.update_world_staged" = "§aĐã chuẩn bị {pack} v{version} cho thế giới '{world}'. Khởi động lại đúng cách sẽ kích hoạt gói với chuyển tiếp địa hình."
"commandpack.update_world_unchanged" = "§aThế giới '{world}' đã dùng {pack} v{version} với cùng thiết lập tạo địa hình. Không cần khởi động lại."
[iris.runtime.studio]
opening = "§6Studio §bĐANG MỞ"
@@ -1691,7 +1695,7 @@ chunk_failed = "Chunk. {x},{z} thất bại: {type}"
"progress.indeterminate" = "{bar}§7 §b{phase}§8 | §f{transferred}§8 | §b{rate}/s"
"progress.detail" = "§8 - §7{detail}"
"progress.complete" = "§aĐã cài đặt gói Iris '{pack}'§8 | §f{transferred}§7 trong §f{elapsed}"
"progress.unchanged" = "§eGói Iris '{pack}' đã được cài đặt."
"progress.unchanged" = "§eGói Iris '{pack}' đã được cài đặt. Dùng overwrite=true để sao lưu và thay thế gói."
"progress.failed" = "§cKhông thể tải gói Iris.§7 Hãy xem lại thông tin tải xuống ở trên rồi thử lại."
"progress.failed_detail" = "§cKhông thể tải gói Iris.§7 {error}"
"progress.cancelled" = "§eĐã hủy tải gói Iris trước khi xuất bản."
@@ -1717,14 +1721,17 @@ check_github = "Kiểm tra xem nó có ở trên không GitHub và báo cáo v
invalid_dimension = "Thư mục chiều không hợp lệ nằm dưới dimensions/."
importing = "Nhập {name} ({key})"
dimension_key_conflict = "Một chiều không gian khác trong thư mục gói là đã sử dụng phím {key}. Nhập thất bại!"
pack_key_conflict = "Name {key}. Nhập thất bại!"
"pack_key_conflict" = "Gói Iris '{key}' đã được cài đặt. Dùng overwrite=true để sao lưu và thay thế gói."
acquired = "Được thành công {name}."
already_installed = "Gói {key} đã được cài đặt, bỏ qua tải xuống."
"already_installed" = "Gói {key} đã được cài đặt, bỏ qua tải xuống. Dùng overwrite=true để sao lưu và thay thế gói."
in_progress = "Một gói Iris khác đang được tải xuống. Hãy chờ quá trình này hoàn tất trước khi thử lại."
validation_failed = "Gói '{pack}'Đã thất bại trong việc xác nhận; thế giới và phòng thu sẽ bị từ chối. Lý do:"
validation_reason = " - {reason}"
validated_with_warnings = { other = "Gói '{pack}Xác nhận với {count} Cảnh báo.", one = "Gói '{pack}Xác nhận với {count} Cảnh báo." }
validated = "Gói '{pack}Xác nhận."
"close_studio" = "Đóng dự án Studio đang hoạt động trước khi thay thế gói đã cài đặt."
"backup_retained" = "Đã lưu gói trước đó tại {path}."
[iris.client.vision]
title = "Iris Tầm nhìn"
+10 -3
View File
@@ -1580,6 +1580,10 @@ failed_fragment = " 失败 {failed}"
"commandpack.compat_header" = "§e资源包 '{pack}': 内容在 Minecraft {version} 中不可用"
"commandpack.compat_none" = "§a资源包 '{pack}' 未使用 Minecraft {version} 中缺失的内容。"
"commandpack.compat_remedy" = "§7将服务器升级到更新的 Minecraft 以恢复这些内容,或声明回退 (维度的 blockFallbacks、方块的 backup)。"
"commandpack.update_world_requires_persistent_world" = "§c请选择已加载的 Iris 世界。Studio 世界无法接收生成更新。"
"commandpack.update_world_staged" = "§a已为世界 '{world}' 暂存 {pack} v{version}。正常重启后将启用该包,并进行地形融合。"
"commandpack.update_world_unchanged" = "§a世界 '{world}' 已使用 {pack} v{version},且生成设置相同。无需重启。"
[iris.runtime.studio]
opening = "§6工作室 §b开幕"
@@ -1691,7 +1695,7 @@ chunk_failed = "块 {x},{z} 失败: {type}"
"progress.indeterminate" = "{bar}§7 §b{phase}§8 | §f{transferred}§8 | §b{rate}/s"
"progress.detail" = "§8 - §7{detail}"
"progress.complete" = "§aIris 包“{pack}”已安装§8 | §f{transferred}§7,用时 §f{elapsed}"
"progress.unchanged" = "§eIris 包“{pack}”已安装。"
"progress.unchanged" = "§eIris 包“{pack}”已安装。 使用 overwrite=true 备份并替换该包。"
"progress.failed" = "§cIris 包下载失败。§7 请查看上方的下载详情后重试。"
"progress.failed_detail" = "§cIris 包下载失败。§7 {error}"
"progress.cancelled" = "§eIris 包下载已在发布前取消。"
@@ -1717,14 +1721,17 @@ check_github = "检查它是否在 GitHub 把这个报告给工作人员!"
invalid_dimension = "无效的维度文件夹 dimensions/."
importing = "导入 {name} ({key})"
dimension_key_conflict = "包文件夹中的另一个维度已经使用密钥 {key}. 导入失败 !"
pack_key_conflict = "另一个包是用钥匙 {key}. 导入失败 !"
"pack_key_conflict" = "Iris 包“{key}”已安装。 使用 overwrite=true 备份并替换该包。"
acquired = "已成功获取 {name}."
already_installed = "包 {key} 已安装,跳过下载。"
"already_installed" = "包 {key} 已安装,跳过下载。 使用 overwrite=true 备份并替换该包。"
in_progress = "另一个 Iris 包下载已在进行中。请等待其完成后再试。"
validation_failed = "包{pack}' 验证失败; 世界和工作室的创建将被拒绝. 原因:"
validation_reason = " - {reason}"
validated_with_warnings = { other = "包{pack}'经验证 {count} 警告! 警告你们!", one = "包{pack}'经验证 {count} 警告,你应该知道" }
validated = "包{pack}已确认。"
"close_studio" = "替换已安装的包之前,请关闭当前 Studio 项目。"
"backup_retained" = "旧包已保存到 {path}。"
[iris.client.vision]
title = "Iris 愿景"
+10 -3
View File
@@ -1580,6 +1580,10 @@ failed_fragment = " 失敗 {failed}"
"commandpack.compat_header" = "§e資源包 '{pack}': 內容在 Minecraft {version} 中無法使用"
"commandpack.compat_none" = "§a資源包 '{pack}' 未使用 Minecraft {version} 中缺少的內容。"
"commandpack.compat_remedy" = "§7將伺服器升級到較新的 Minecraft 以還原這些內容,或宣告備援 (維度的 blockFallbacks、方塊的 backup)。"
"commandpack.update_world_requires_persistent_world" = "§c請選擇已載入的 Iris 世界。Studio 世界無法接收生成更新。"
"commandpack.update_world_staged" = "§a已為世界 '{world}' 暫存 {pack} v{version}。正常重新啟動後將啟用此套件,並進行地形融合。"
"commandpack.update_world_unchanged" = "§a世界 '{world}' 已使用 {pack} v{version},且生成設定相同。無需重新啟動。"
[iris.runtime.studio]
opening = "§6工作室 §b開幕"
@@ -1691,7 +1695,7 @@ chunk_failed = "塊 {x},{z} 失敗: {type}"
"progress.indeterminate" = "{bar}§7 §b{phase}§8 | §f{transferred}§8 | §b{rate}/s"
"progress.detail" = "§8 - §7{detail}"
"progress.complete" = "§aIris 套件「{pack}」已安裝§8 | §f{transferred}§7,用時 §f{elapsed}"
"progress.unchanged" = "§eIris 套件「{pack}」已安裝。"
"progress.unchanged" = "§eIris 套件「{pack}」已安裝。 使用 overwrite=true 備份並替換此套件。"
"progress.failed" = "§cIris 套件下載失敗。§7 請檢視上方的下載詳細資訊後重試。"
"progress.failed_detail" = "§cIris 套件下載失敗。§7 {error}"
"progress.cancelled" = "§eIris 套件下載已在發佈前取消。"
@@ -1717,14 +1721,17 @@ check_github = "檢查它是否在 GitHub 把這個報告給工作人員!"
invalid_dimension = "無效的維度資料夾 dimensions/."
importing = "匯入 {name} ({key})"
dimension_key_conflict = "包資料夾中的另一個維度已經使用金鑰 {key}. 匯入失敗 !"
pack_key_conflict = "另一個包是用鑰匙 {key}. 匯入失敗 !"
"pack_key_conflict" = "Iris 套件「{key}」已安裝。 使用 overwrite=true 備份並替換此套件。"
acquired = "已成功獲取 {name}."
already_installed = "套件 {key} 已安裝,跳過下載。"
"already_installed" = "套件 {key} 已安裝,跳過下載。 使用 overwrite=true 備份並替換此套件。"
in_progress = "另一個 Iris 套件下載已在進行中。請等待其完成後再試。"
validation_failed = "包{pack}' 驗證失敗; 世界和工作室的建立將被拒絕. 原因:"
validation_reason = " - {reason}"
validated_with_warnings = { other = "包{pack}'經驗證 {count} 警告! 警告你們!", one = "包{pack}'經驗證 {count} 警告,你應該知道" }
validated = "包{pack}已確認。"
"close_studio" = "替換已安裝的套件之前,請關閉目前的 Studio 專案。"
"backup_retained" = "舊套件已儲存至 {path}。"
[iris.client.vision]
title = "Iris 願景"
@@ -1,11 +1,28 @@
package art.arcane.iris.diagnostics.splash;
import art.arcane.iris.pack.BuiltInPackUpdates;
import org.junit.Test;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
public class IrisSplashComposerTest {
@Test
public void packLinesShowUpdatesAndKeepCustomPacks() {
List<IrisSplashPackScanner.SplashPackMetadata> packs = List.of(
new IrisSplashPackScanner.SplashPackMetadata("custom", "7"),
new IrisSplashPackScanner.SplashPackMetadata("overworld", "4009"),
new IrisSplashPackScanner.SplashPackMetadata("underworld", "1012"));
assertEquals(List.of("Custom Dimensions: 3", " custom v7", " overworld v4009 -> v4010 available",
" underworld v1012", "Update overworld: /iris download pack=overworld overwrite=true"),
IrisSplashComposer.composePackLines(packs, Map.of(
"overworld", new BuiltInPackUpdates.Update("4010"),
"underworld", new BuiltInPackUpdates.Update("1012"))));
}
@Test
public void composeInfoUsesCurrentVersionWithoutStaleReleaseTag() {
String[] info = IrisSplashComposer.composeInfo("4.0.0-26.2", "Paper 26.2", IrisSplashComposer.InfoStyle.PLAIN);
@@ -18,6 +18,31 @@ public class AtomicDirectoryPublisherTest {
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
@Test
public void retainedBackupSurvivesCommitAndRemainsAvailableForRollback() throws Exception {
Path root = temporaryFolder.getRoot().toPath();
Path target = Files.createDirectory(root.resolve("target"));
Files.writeString(target.resolve("value.txt"), "old");
Path staged = Files.createDirectory(root.resolve("stage"));
Files.writeString(staged.resolve("value.txt"), "new");
Path retained = root.resolve("retained");
try (AtomicDirectoryPublisher.Publication publication = AtomicDirectoryPublisher.publish(staged, target)) {
assertTrue(publication.retainBackup(retained));
}
assertEquals("old", Files.readString(target.resolve("value.txt")));
assertFalse(Files.exists(retained));
Files.createDirectory(staged);
Files.writeString(staged.resolve("value.txt"), "new");
try (AtomicDirectoryPublisher.Publication publication = AtomicDirectoryPublisher.publish(staged, target)) {
assertTrue(publication.retainBackup(retained));
publication.commit();
}
assertEquals("old", Files.readString(retained.resolve("value.txt")));
assertEquals("new", Files.readString(target.resolve("value.txt")));
}
@Test
public void commitPublishesStagedDirectoryAndRemovesBackup() throws Exception {
Path root = temporaryFolder.getRoot().toPath();
@@ -0,0 +1,129 @@
package art.arcane.iris.pack;
import com.sun.net.httpserver.HttpServer;
import org.junit.Test;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.http.HttpClient;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class BuiltInPackUpdatesTest {
@Test
public void comparesNumericVersionsWithoutDowngradeNotices() {
BuiltInPackUpdates.Update update = new BuiltInPackUpdates.Update("4010");
assertEquals(" -> v4010 available", update.suffix("999"));
assertEquals("", update.suffix("4010"));
assertEquals("", update.suffix("5000"));
assertEquals(" (latest v4010)", update.suffix("custom"));
assertFalse(update.newerThan("custom"));
assertEquals(" (update check unavailable)", BuiltInPackUpdates.Update.unavailable().suffix("4010"));
}
@Test
public void checksOnlyBuiltInPacksAndKeepsSuccessfulResultsOnPartialFailure() throws Exception {
AtomicInteger requests = new AtomicInteger();
HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
server.createContext("/", exchange -> {
requests.incrementAndGet();
boolean overworld = exchange.getRequestURI().getPath().equals("/overworld");
byte[] body = (overworld ? release("v4010", "overworld.zip", false) : "rate limited")
.getBytes(StandardCharsets.UTF_8);
exchange.sendResponseHeaders(overworld ? 200 : 429, body.length);
exchange.getResponseBody().write(body);
exchange.close();
});
server.start();
try (HttpClient client = HttpClient.newHttpClient()) {
Map<String, BuiltInPackUpdates.Update> updates = BuiltInPackUpdates.check(
List.of("overworld", "underworld", "custom", "overworld"), client,
pack -> URI.create("http://127.0.0.1:" + server.getAddress().getPort() + "/" + pack))
.get(10, TimeUnit.SECONDS);
assertEquals(2, requests.get());
assertEquals("4010", updates.get("overworld").version());
assertEquals(BuiltInPackUpdates.Update.unavailable(), updates.get("underworld"));
assertFalse(updates.containsKey("custom"));
} finally {
server.stop(0);
}
}
@Test
public void rejectsMalformedPrereleaseAndMissingArchiveMetadata() throws Exception {
for (String body : List.of("invalid", "{}", release("4010", "source.zip", false),
release("4010", "overworld.zip", true), release("not-a-version", "overworld.zip", false))) {
assertEquals(BuiltInPackUpdates.Update.unavailable(), checkResponse(body));
}
}
@Test
public void acceptsStableReleaseWithMatchingArchive() throws Exception {
assertTrue(checkResponse(release("V+4010", "overworld.zip", false)).newerThan("4009"));
}
@Test
public void boundsReleaseMetadataSize() throws Exception {
assertEquals(BuiltInPackUpdates.Update.unavailable(), checkResponse(" ".repeat(1024 * 1024 + 1)));
}
@Test
public void timesOutWithoutResponseHeaders() throws Exception {
assertStalledResponseUnavailable(false);
}
@Test
public void timesOutWhenResponseBodyStalls() throws Exception {
assertStalledResponseUnavailable(true);
}
private void assertStalledResponseUnavailable(boolean sendHeaders) throws Exception {
HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
server.createContext("/", exchange -> {
if (sendHeaders) {
exchange.sendResponseHeaders(200, 100);
exchange.getResponseBody().write('{');
exchange.getResponseBody().flush();
}
});
server.start();
try (HttpClient client = HttpClient.newHttpClient()) {
assertEquals(BuiltInPackUpdates.Update.unavailable(), BuiltInPackUpdates.check(
List.of("overworld"), client,
pack -> URI.create("http://127.0.0.1:" + server.getAddress().getPort() + "/"))
.get(8, TimeUnit.SECONDS).get("overworld"));
} finally {
server.stop(0);
}
}
private BuiltInPackUpdates.Update checkResponse(String response) throws Exception {
HttpServer server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
server.createContext("/", exchange -> {
byte[] body = response.getBytes(StandardCharsets.UTF_8);
exchange.sendResponseHeaders(200, body.length);
exchange.getResponseBody().write(body);
exchange.close();
});
server.start();
try (HttpClient client = HttpClient.newHttpClient()) {
return BuiltInPackUpdates.check(List.of("overworld"), client,
pack -> URI.create("http://127.0.0.1:" + server.getAddress().getPort() + "/"))
.get(10, TimeUnit.SECONDS).get("overworld");
} finally {
server.stop(0);
}
}
private static String release(String version, String asset, boolean prerelease) {
return "{\"tag_name\":\"" + version + "\",\"draft\":false,\"prerelease\":" + prerelease
+ ",\"assets\":[{\"name\":\"" + asset + "\"}]}";
}
}
@@ -606,6 +606,13 @@ public class PackDownloaderTest {
assertTrue(result.restartRequired());
assertEquals("new", Files.readString(target.toPath().resolve("state.txt"), StandardCharsets.UTF_8));
assertFalse(Files.exists(target.toPath().resolve("old-only.txt")));
File[] backups = new File(packsFolder, ".backups").listFiles();
assertNotNull(backups);
assertEquals(1, backups.length);
assertEquals("old", Files.readString(backups[0].toPath().resolve("state.txt")));
assertEquals("old", Files.readString(backups[0].toPath().resolve("old-only.txt")));
assertTrue(feedback.stream().anyMatch(message -> message.contains(backups[0].getPath())));
assertFalse(PackDirectoryResolver.isVisiblePackDirectory(new File(packsFolder, ".backups")));
assertTransactionStateClean(packsFolder);
assertEquals(0, PackDownloader.downloadLockCount());
}
@@ -7,7 +7,12 @@ import art.arcane.iris.pack.BrokenPackException;
import art.arcane.iris.pack.PackValidationRegistry;
import art.arcane.iris.pack.PackValidationResult;
import art.arcane.iris.generation.runtime.PreservationRegistry;
import art.arcane.iris.generation.terrain.IrisDimension;
import art.arcane.iris.platform.bukkit.plugin.VolmitSender;
import art.arcane.iris.spi.IrisLogging;
import art.arcane.iris.spi.IrisServices;
import art.arcane.iris.world.history.GenerationHistory;
import art.arcane.iris.world.task.J;
import art.arcane.iris.testsupport.PlatformLeakGuard;
import org.junit.Assume;
import org.junit.After;
@@ -35,6 +40,7 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
public class StudioSVCWorldPackPublishTest {
@ClassRule
@@ -54,6 +60,34 @@ public class StudioSVCWorldPackPublishTest {
IrisServices.remove(PreservationRegistry.class);
}
@Test
public void brokenSourceIsRejectedBeforeOpeningOrChangingWorldHistory() throws Exception {
Path source = temporaryFolder.newFolder("invalid-generation-update").toPath();
writeValidPack(source);
Files.writeString(source.resolve("biomes/biome.json"), "{");
Path world = temporaryFolder.newFolder("untouched-generation-world").toPath();
Files.writeString(world.resolve("level.dat"), "world-state");
IrisData sourceData = mock(IrisData.class);
when(sourceData.getDataFolder()).thenReturn(source.toFile());
IrisDimension dimension = new IrisDimension();
dimension.setLoadKey("main");
dimension.setLoader(sourceData);
VolmitSender sender = mock(VolmitSender.class);
try (MockedStatic<J> scheduler = mockStatic(J.class);
MockedStatic<IrisLogging> logging = mockStatic(IrisLogging.class);
MockedStatic<GenerationHistory> history = mockStatic(GenerationHistory.class);
MockedStatic<ServerConfigurator> configurator = mockStatic(ServerConfigurator.class)) {
assertNull(new StudioSVC().replaceIntoWorld(sender, dimension, world.toFile(), 42L));
history.verifyNoInteractions();
configurator.verifyNoInteractions();
assertFalse(Files.exists(world.resolve("iris")));
assertEquals("world-state", Files.readString(world.resolve("level.dat")));
assertTrue(PackValidationRegistry.isBroken(source));
}
}
@Test
public void copiesToStageAndPublishesTheCompletePack() throws IOException {
Path root = temporaryFolder.newFolder("world").toPath();