mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-08-27 12:41:43 +00:00
dwa
This commit is contained in:
@@ -1,3 +1,3 @@
|
|||||||
[01:34:50] [Test worker/INFO]: [STDERR]: [Iris/WARN] Native structure terrain envelope at 0,0 was clipped to Minecraft's 8-chunk structure reference range
|
[10:47:09] [Test worker/INFO]: [STDERR]: [Iris/WARN] Native structure terrain envelope at 0,0 was clipped to Minecraft's 8-chunk structure reference range
|
||||||
[01:34:50] [Test worker/INFO]: [STDERR]: [Iris/WARN] Native structure burial at 0,0 clamped to world floor: wanted -19, used -4
|
[10:47:09] [Test worker/INFO]: [STDERR]: [Iris/WARN] Native structure burial at 0,0 clamped to world floor: wanted -19, used -4
|
||||||
[01:34:50] [Test worker/INFO]: [STDERR]: [Iris/WARN] Native structure burial at 0,0 clamped to world floor: wanted -5, used -2
|
[10:47:09] [Test worker/INFO]: [STDERR]: [Iris/WARN] Native structure burial at 0,0 clamped to world floor: wanted -5, used -2
|
||||||
|
|||||||
+6
-2
@@ -53,15 +53,19 @@ final class VanillaStructureBiomes {
|
|||||||
if (structure == null) {
|
if (structure == null) {
|
||||||
throw new IllegalArgumentException("Registered structure does not exist: " + structureKey);
|
throw new IllegalArgumentException("Registered structure does not exist: " + structureKey);
|
||||||
}
|
}
|
||||||
|
boolean hasFilterEntries = false;
|
||||||
for (Holder<Biome> holder : structure.biomes()) {
|
for (Holder<Biome> holder : structure.biomes()) {
|
||||||
|
hasFilterEntries = true;
|
||||||
Optional<ResourceKey<Biome>> key = holder.unwrapKey();
|
Optional<ResourceKey<Biome>> key = holder.unwrapKey();
|
||||||
if (key.isPresent()) {
|
if (key.isPresent()) {
|
||||||
keys.add(key.get().identifier().toString());
|
keys.add(key.get().identifier().toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (keys.isEmpty()) {
|
// An empty biome filter is legal datapack content (opt-in structures whose tags only
|
||||||
|
// reference absent modded biomes); the structure is unreachable, not an error state.
|
||||||
|
if (keys.isEmpty() && hasFilterEntries) {
|
||||||
throw new IllegalStateException("Registered structure '" + structureKey
|
throw new IllegalStateException("Registered structure '" + structureKey
|
||||||
+ "' exposes no registered biome keys");
|
+ "' has biome filter entries but none resolve to registered biome keys");
|
||||||
}
|
}
|
||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
|
|||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
package art.arcane.iris.core.nms.v26_2_R1;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A registered structure with an EMPTY biome filter is legal datapack content: opt-in structures
|
||||||
|
* (e.g. Towns and Towers' towns_and_towers:exclusives/*) ship biome tags that only reference
|
||||||
|
* optional modded biomes, so the filter resolves to zero entries on a vanilla server. Resolving
|
||||||
|
* biome keys for such a structure must return an empty set (the structure is simply unreachable),
|
||||||
|
* never throw — a throw here aborts the whole /iris structure verify run and degrades /iris find.
|
||||||
|
* The defensive throw is only kept for a NON-empty filter whose holders all fail to resolve keys.
|
||||||
|
*/
|
||||||
|
public class VanillaStructureBiomesEmptyFilterContractTest {
|
||||||
|
@Test
|
||||||
|
public void emptyBiomeFilterReturnsEmptyKeysInsteadOfThrowing() throws IOException {
|
||||||
|
String source = Files.readString(Path.of(System.getProperty("iris.vanillaStructureBiomesSource")));
|
||||||
|
|
||||||
|
assertTrue(source.contains("if (keys.isEmpty() && hasFilterEntries)"));
|
||||||
|
assertTrue(source.contains("has biome filter entries but none resolve to registered biome keys"));
|
||||||
|
assertFalse(source.contains("exposes no registered biome keys"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void moddedHookMatchesEmptyFilterContract() throws IOException {
|
||||||
|
String source = Files.readString(Path.of(System.getProperty("iris.moddedStructureHooksSource")));
|
||||||
|
|
||||||
|
assertTrue(source.contains("if (keys.isEmpty() && hasFilterEntries)"));
|
||||||
|
assertTrue(source.contains("has biome filter entries but none resolve to registered biome keys"));
|
||||||
|
assertFalse(source.contains("exposes no registered biome keys"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -581,6 +581,10 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
|
|||||||
watch::normalizeSettingsContent
|
watch::normalizeSettingsContent
|
||||||
);
|
);
|
||||||
configHotloadEngine.configure(3_000L, List.of(watch.settingsFile()), List.of());
|
configHotloadEngine.configure(3_000L, List.of(watch.settingsFile()), List.of());
|
||||||
|
// Stale-temp cleanup must complete before services enable: StudioSVC.onEnable downloads
|
||||||
|
// packs through cache/temp on an async thread, and a concurrent delete of that folder
|
||||||
|
// truncated pack imports mid-copy (partial packs/<key> without dimensions/).
|
||||||
|
IO.delete(getTemp());
|
||||||
services.values().forEach(IrisService::onEnable);
|
services.values().forEach(IrisService::onEnable);
|
||||||
services.values().forEach(this::registerListener);
|
services.values().forEach(this::registerListener);
|
||||||
addShutdownHook();
|
addShutdownHook();
|
||||||
@@ -593,7 +597,6 @@ public class Iris extends VolmitPlugin implements Listener, ReloadAware {
|
|||||||
}
|
}
|
||||||
|
|
||||||
J.s(() -> {
|
J.s(() -> {
|
||||||
J.a(() -> IO.delete(getTemp()));
|
|
||||||
J.a(this::bstats);
|
J.a(this::bstats);
|
||||||
J.ar(() -> settingsHotloadWatch.checkConfigHotload(configHotloadEngine), 60);
|
J.ar(() -> settingsHotloadWatch.checkConfigHotload(configHotloadEngine), 60);
|
||||||
J.sr(this::tickQueue, 0);
|
J.sr(this::tickQueue, 0);
|
||||||
|
|||||||
+1
-1
@@ -57,7 +57,7 @@ public final class BukkitWorldReconciler {
|
|||||||
assert dim != null && gen != null;
|
assert dim != null && gen != null;
|
||||||
|
|
||||||
Iris.info(C.LIGHT_PURPLE + "Preparing Spawn for " + s + "' using Iris:" + generator + "...");
|
Iris.info(C.LIGHT_PURPLE + "Preparing Spawn for " + s + "' using Iris:" + generator + "...");
|
||||||
WorldCreator c = WorldCreator.ofKey(worldKey)
|
WorldCreator c = WorldCreatorCompat.ofKey(worldKey)
|
||||||
.generator(gen)
|
.generator(gen)
|
||||||
.environment(BukkitEnvironment.from(dim.getEnvironment()));
|
.environment(BukkitEnvironment.from(dim.getEnvironment()));
|
||||||
Long stagedSeed = IrisWorlds.readBukkitWorldSeed(s);
|
Long stagedSeed = IrisWorlds.readBukkitWorldSeed(s);
|
||||||
|
|||||||
+11
@@ -22,9 +22,11 @@ import art.arcane.iris.Iris;
|
|||||||
import art.arcane.iris.core.lifecycle.WorldLifecycleStaging;
|
import art.arcane.iris.core.lifecycle.WorldLifecycleStaging;
|
||||||
import art.arcane.iris.core.loader.IrisData;
|
import art.arcane.iris.core.loader.IrisData;
|
||||||
import art.arcane.iris.core.pack.BrokenPackException;
|
import art.arcane.iris.core.pack.BrokenPackException;
|
||||||
|
import art.arcane.iris.core.pack.PackDownloader;
|
||||||
import art.arcane.iris.core.pack.PackValidationRegistry;
|
import art.arcane.iris.core.pack.PackValidationRegistry;
|
||||||
import art.arcane.iris.core.pack.PackValidationResult;
|
import art.arcane.iris.core.pack.PackValidationResult;
|
||||||
import art.arcane.iris.core.pack.PackValidator;
|
import art.arcane.iris.core.pack.PackValidator;
|
||||||
|
import art.arcane.iris.spi.IrisPlatforms;
|
||||||
import art.arcane.iris.core.service.StudioSVC;
|
import art.arcane.iris.core.service.StudioSVC;
|
||||||
import art.arcane.iris.engine.object.IrisDimension;
|
import art.arcane.iris.engine.object.IrisDimension;
|
||||||
import art.arcane.iris.engine.object.IrisWorld;
|
import art.arcane.iris.engine.object.IrisWorld;
|
||||||
@@ -61,6 +63,9 @@ public final class IrisWorldGeneratorResolver {
|
|||||||
}
|
}
|
||||||
PackValidationRegistry.clear();
|
PackValidationRegistry.clear();
|
||||||
for (File packDir : packDirs) {
|
for (File packDir : packDirs) {
|
||||||
|
if (packDir.getName().contains(".importing-")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
PackValidationResult result = PackValidator.validate(packDir);
|
PackValidationResult result = PackValidator.validate(packDir);
|
||||||
PackValidationRegistry.publish(result);
|
PackValidationRegistry.publish(result);
|
||||||
@@ -90,6 +95,12 @@ public final class IrisWorldGeneratorResolver {
|
|||||||
IrisDimension dimension = pack.isDirectory() ? IrisData.get(pack).getDimensionLoader().load(id) : null;
|
IrisDimension dimension = pack.isDirectory() ? IrisData.get(pack).getDimensionLoader().load(id) : null;
|
||||||
if (dimension == null) dimension = IrisData.loadAnyDimension(id, null);
|
if (dimension == null) dimension = IrisData.loadAnyDimension(id, null);
|
||||||
if (dimension == null) {
|
if (dimension == null) {
|
||||||
|
File packsRoot = IrisPlatforms.get().dataFolderNoCreate(StudioSVC.WORKSPACE_NAME);
|
||||||
|
if (PackDownloader.isPackPresent(packsRoot, id)) {
|
||||||
|
Iris.error("Pack '" + id + "' exists at " + new File(packsRoot, id).getPath()
|
||||||
|
+ " but its dimension failed to load; not redownloading. Fix or delete the pack folder.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
Iris.warn("Unable to find dimension type " + id + " Looking for online packs...");
|
Iris.warn("Unable to find dimension type " + id + " Looking for online packs...");
|
||||||
Iris.service(StudioSVC.class).downloadSearch(new VolmitSender(Bukkit.getConsoleSender()), id, false);
|
Iris.service(StudioSVC.class).downloadSearch(new VolmitSender(Bukkit.getConsoleSender()), id, false);
|
||||||
dimension = IrisData.loadAnyDimension(id, null);
|
dimension = IrisData.loadAnyDimension(id, null);
|
||||||
|
|||||||
@@ -40,8 +40,7 @@ import art.arcane.volmlib.util.collection.KList;
|
|||||||
import art.arcane.volmlib.util.director.DirectorOrigin;
|
import art.arcane.volmlib.util.director.DirectorOrigin;
|
||||||
import art.arcane.volmlib.util.director.annotations.Director;
|
import art.arcane.volmlib.util.director.annotations.Director;
|
||||||
import art.arcane.volmlib.util.director.annotations.Param;
|
import art.arcane.volmlib.util.director.annotations.Param;
|
||||||
import io.papermc.paper.registry.RegistryAccess;
|
import org.bukkit.Bukkit;
|
||||||
import io.papermc.paper.registry.RegistryKey;
|
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.NamespacedKey;
|
import org.bukkit.NamespacedKey;
|
||||||
import org.bukkit.Registry;
|
import org.bukkit.Registry;
|
||||||
@@ -187,7 +186,10 @@ public class CommandFind implements DirectorExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static Structure resolveNativeStructure(String structureKey) {
|
private static Structure resolveNativeStructure(String structureKey) {
|
||||||
Registry<Structure> structureRegistry = RegistryAccess.registryAccess().getRegistry(RegistryKey.STRUCTURE);
|
Registry<Structure> structureRegistry = Bukkit.getRegistry(Structure.class);
|
||||||
|
if (structureRegistry == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
for (Structure candidate : structureRegistry) {
|
for (Structure candidate : structureRegistry) {
|
||||||
NamespacedKey key = structureRegistry.getKey(candidate);
|
NamespacedKey key = structureRegistry.getKey(candidate);
|
||||||
if (key != null && key.toString().equalsIgnoreCase(structureKey)) {
|
if (key != null && key.toString().equalsIgnoreCase(structureKey)) {
|
||||||
|
|||||||
+7
-7
@@ -49,8 +49,6 @@ import art.arcane.iris.engine.platform.PlatformChunkGenerator;
|
|||||||
import art.arcane.iris.util.common.plugin.VolmitSender;
|
import art.arcane.iris.util.common.plugin.VolmitSender;
|
||||||
import art.arcane.volmlib.util.math.RNG;
|
import art.arcane.volmlib.util.math.RNG;
|
||||||
import art.arcane.iris.util.common.scheduling.J;
|
import art.arcane.iris.util.common.scheduling.J;
|
||||||
import io.papermc.paper.registry.RegistryAccess;
|
|
||||||
import io.papermc.paper.registry.RegistryKey;
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.NamespacedKey;
|
import org.bukkit.NamespacedKey;
|
||||||
@@ -146,11 +144,13 @@ public class CommandStructure implements DirectorExecutor {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
KList<String> structureKeys = new KList<>();
|
KList<String> structureKeys = new KList<>();
|
||||||
Registry<Structure> structureRegistry = RegistryAccess.registryAccess().getRegistry(RegistryKey.STRUCTURE);
|
Registry<Structure> structureRegistry = Bukkit.getRegistry(Structure.class);
|
||||||
for (Structure structure : structureRegistry) {
|
if (structureRegistry != null) {
|
||||||
NamespacedKey key = structureRegistry.getKey(structure);
|
for (Structure structure : structureRegistry) {
|
||||||
if (key != null) {
|
NamespacedKey key = structureRegistry.getKey(structure);
|
||||||
structureKeys.add(key.toString());
|
if (key != null) {
|
||||||
|
structureKeys.add(key.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
VolmitSender commandSender = sender();
|
VolmitSender commandSender = sender();
|
||||||
|
|||||||
+10
-3
@@ -1,6 +1,7 @@
|
|||||||
package art.arcane.iris.core.service;
|
package art.arcane.iris.core.commands;
|
||||||
|
|
||||||
import art.arcane.iris.Iris;
|
import art.arcane.iris.Iris;
|
||||||
|
import art.arcane.iris.core.service.CommandSVC;
|
||||||
import io.papermc.paper.command.brigadier.BasicCommand;
|
import io.papermc.paper.command.brigadier.BasicCommand;
|
||||||
import io.papermc.paper.command.brigadier.CommandSourceStack;
|
import io.papermc.paper.command.brigadier.CommandSourceStack;
|
||||||
import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;
|
import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;
|
||||||
@@ -9,13 +10,19 @@ import org.bukkit.command.CommandSender;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
final class PaperCommandRegistrar {
|
/**
|
||||||
|
* Paper-only command registration. Lives outside {@code art.arcane.iris.core.service} on purpose:
|
||||||
|
* that package is eagerly Class.forName-scanned by JarScanner during enable, and resolving this
|
||||||
|
* class on plain Spigot throws NoClassDefFoundError for the Paper lifecycle types. CommandSVC
|
||||||
|
* loads it reflectively only when the Bukkit plugin.yml command is unavailable (Paper path).
|
||||||
|
*/
|
||||||
|
public final class PaperCommandRegistrar {
|
||||||
private static final String ROOT_COMMAND = "iris";
|
private static final String ROOT_COMMAND = "iris";
|
||||||
|
|
||||||
private PaperCommandRegistrar() {
|
private PaperCommandRegistrar() {
|
||||||
}
|
}
|
||||||
|
|
||||||
static void register(Iris plugin, CommandSVC commandService) {
|
public static void register(Iris plugin, CommandSVC commandService) {
|
||||||
plugin.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event ->
|
plugin.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event ->
|
||||||
event.registrar().register(
|
event.registrar().register(
|
||||||
ROOT_COMMAND,
|
ROOT_COMMAND,
|
||||||
@@ -159,7 +159,7 @@ public class CommandSVC implements IrisService, CommandExecutor, TabCompleter, D
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void executeRoot(CommandSender sender, String label, String[] args) {
|
public void executeRoot(CommandSender sender, String label, String[] args) {
|
||||||
if (!sender.hasPermission(ROOT_PERMISSION)) {
|
if (!sender.hasPermission(ROOT_PERMISSION)) {
|
||||||
sender.sendMessage(IrisLanguage.text(
|
sender.sendMessage(IrisLanguage.text(
|
||||||
IrisMessages.COMMAND_PERMISSION_DENIED,
|
IrisMessages.COMMAND_PERMISSION_DENIED,
|
||||||
@@ -171,7 +171,7 @@ public class CommandSVC implements IrisService, CommandExecutor, TabCompleter, D
|
|||||||
J.aBukkit(() -> executeCommand(sender, label, args));
|
J.aBukkit(() -> executeCommand(sender, label, args));
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> tabCompleteRoot(CommandSender sender, String alias, String[] args) {
|
public List<String> tabCompleteRoot(CommandSender sender, String alias, String[] args) {
|
||||||
List<String> suggestions = runDirectorTab(sender, alias, args);
|
List<String> suggestions = runDirectorTab(sender, alias, args);
|
||||||
if (sender instanceof Player player && IrisSettings.get().getGeneral().isCommandSounds()) {
|
if (sender instanceof Player player && IrisSettings.get().getGeneral().isCommandSounds()) {
|
||||||
player.playSound(player.getLocation(), Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.25f, RNG.r.f(0.125f, 1.95f));
|
player.playSound(player.getLocation(), Sound.BLOCK_AMETHYST_BLOCK_CHIME, 0.25f, RNG.r.f(0.125f, 1.95f));
|
||||||
@@ -190,7 +190,7 @@ public class CommandSVC implements IrisService, CommandExecutor, TabCompleter, D
|
|||||||
private void registerPaperCommand() {
|
private void registerPaperCommand() {
|
||||||
try {
|
try {
|
||||||
Class<?> registrarType = Class.forName(
|
Class<?> registrarType = Class.forName(
|
||||||
"art.arcane.iris.core.service.PaperCommandRegistrar",
|
"art.arcane.iris.core.commands.PaperCommandRegistrar",
|
||||||
true,
|
true,
|
||||||
getClass().getClassLoader()
|
getClass().getClassLoader()
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ final class FellingRun {
|
|||||||
volatile int effectStride = 1;
|
volatile int effectStride = 1;
|
||||||
volatile ItemStack expectedTool;
|
volatile ItemStack expectedTool;
|
||||||
volatile List<TreeMember> work = List.of();
|
volatile List<TreeMember> work = List.of();
|
||||||
|
volatile String abortReason;
|
||||||
|
|
||||||
FellingRun(
|
FellingRun(
|
||||||
TreeClaim claim,
|
TreeClaim claim,
|
||||||
|
|||||||
+50
-4
@@ -67,6 +67,9 @@ final class TreeFellingRunner {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
List<TreeMarkerTraversal.Position> positions = positionsForFelling(discovery, run.candidate.trigger());
|
List<TreeMarkerTraversal.Position> positions = positionsForFelling(discovery, run.candidate.trigger());
|
||||||
|
if (!discovery.complete()) {
|
||||||
|
run.abortReason = "discovery-incomplete";
|
||||||
|
}
|
||||||
preflight(run, positions, discovery.complete());
|
preflight(run, positions, discovery.complete());
|
||||||
} catch (Throwable error) {
|
} catch (Throwable error) {
|
||||||
IrisLogging.reportError("Failed to discover an Iris tree for felling.", error);
|
IrisLogging.reportError("Failed to discover an Iris tree for felling.", error);
|
||||||
@@ -96,6 +99,7 @@ final class TreeFellingRunner {
|
|||||||
Runnable task = () -> {
|
Runnable task = () -> {
|
||||||
try {
|
try {
|
||||||
if (!run.candidate.world().isChunkLoaded(chunk.x(), chunk.z())) {
|
if (!run.candidate.world().isChunkLoaded(chunk.x(), chunk.z())) {
|
||||||
|
run.abortReason = "chunk-unloaded";
|
||||||
failed.set(true);
|
failed.set(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -151,6 +155,7 @@ final class TreeFellingRunner {
|
|||||||
|
|
||||||
List<TreeMember> ordered = orderMembers(run.candidate.trigger(), members);
|
List<TreeMember> ordered = orderMembers(run.candidate.trigger(), members);
|
||||||
if (ordered.isEmpty() || !ordered.getFirst().position().equals(run.candidate.trigger())) {
|
if (ordered.isEmpty() || !ordered.getFirst().position().equals(run.candidate.trigger())) {
|
||||||
|
run.abortReason = "trigger-drift";
|
||||||
finish(run);
|
finish(run);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -212,8 +217,9 @@ final class TreeFellingRunner {
|
|||||||
"Failed to prepare an Iris tree-feller block.",
|
"Failed to prepare an Iris tree-feller block.",
|
||||||
() -> prepareBreak(run, member)
|
() -> prepareBreak(run, member)
|
||||||
);
|
);
|
||||||
if (!J.runRegion(run.candidate.world(), chunk.x(), chunk.z(), task)) {
|
if (!runMemberRegion(run, chunk.x(), chunk.z(), task)) {
|
||||||
if (member.log()) {
|
if (member.log()) {
|
||||||
|
run.abortReason = "schedule-refused";
|
||||||
finish(run);
|
finish(run);
|
||||||
} else {
|
} else {
|
||||||
continueRun(run);
|
continueRun(run);
|
||||||
@@ -221,10 +227,34 @@ final class TreeFellingRunner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// On Paper, J.runRegion/J.runEntity always defer a full tick even when already on the
|
||||||
|
// main thread, which collapsed the pulse pacing to one block per tick and forced players
|
||||||
|
// to hold sneak for 10+ seconds on large trees. Folia keeps the scheduler hop.
|
||||||
|
static boolean shouldRunInline(boolean folia, boolean primaryThread) {
|
||||||
|
return !folia && primaryThread;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean runMemberRegion(FellingRun run, int chunkX, int chunkZ, Runnable task) {
|
||||||
|
if (shouldRunInline(J.isFolia(), Bukkit.isPrimaryThread())) {
|
||||||
|
task.run();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return J.runRegion(run.candidate.world(), chunkX, chunkZ, task);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean runMemberEntity(FellingRun run, Runnable task) {
|
||||||
|
if (shouldRunInline(J.isFolia(), Bukkit.isPrimaryThread())) {
|
||||||
|
task.run();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return J.runEntity(run.candidate.player(), task);
|
||||||
|
}
|
||||||
|
|
||||||
private void prepareBreak(FellingRun run, TreeMember member) {
|
private void prepareBreak(FellingRun run, TreeMember member) {
|
||||||
Block block = liveMemberBlock(run, member);
|
Block block = liveMemberBlock(run, member);
|
||||||
if (block == null) {
|
if (block == null) {
|
||||||
if (member.log()) {
|
if (member.log()) {
|
||||||
|
run.abortReason = "member-drift";
|
||||||
finish(run);
|
finish(run);
|
||||||
} else {
|
} else {
|
||||||
continueRun(run);
|
continueRun(run);
|
||||||
@@ -247,7 +277,8 @@ final class TreeFellingRunner {
|
|||||||
"Failed to reserve Iris tree-feller tool durability.",
|
"Failed to reserve Iris tree-feller tool durability.",
|
||||||
() -> reserveDamage(run, member)
|
() -> reserveDamage(run, member)
|
||||||
);
|
);
|
||||||
if (!J.runEntity(run.candidate.player(), task)) {
|
if (!runMemberEntity(run, task)) {
|
||||||
|
run.abortReason = "schedule-refused";
|
||||||
finish(run);
|
finish(run);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -255,6 +286,7 @@ final class TreeFellingRunner {
|
|||||||
private void reserveDamage(FellingRun run, TreeMember member) {
|
private void reserveDamage(FellingRun run, TreeMember member) {
|
||||||
Player player = run.candidate.player();
|
Player player = run.candidate.player();
|
||||||
if (!isRunControlActive(run, player)) {
|
if (!isRunControlActive(run, player)) {
|
||||||
|
run.abortReason = "control-released";
|
||||||
finish(run);
|
finish(run);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -264,11 +296,13 @@ final class TreeFellingRunner {
|
|||||||
|| inventory.getHeldItemSlot() != run.heldSlot
|
|| inventory.getHeldItemSlot() != run.heldSlot
|
||||||
|| !current.isSimilar(run.expectedTool)
|
|| !current.isSimilar(run.expectedTool)
|
||||||
|| !TreeFellerSVC.isAxe(current)) {
|
|| !TreeFellerSVC.isAxe(current)) {
|
||||||
|
run.abortReason = "tool-changed";
|
||||||
finish(run);
|
finish(run);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!reserveLogCost(run)) {
|
if (!reserveLogCost(run)) {
|
||||||
|
run.abortReason = "log-cost-refused";
|
||||||
finish(run);
|
finish(run);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -304,8 +338,8 @@ final class TreeFellingRunner {
|
|||||||
Runnable task = () -> runMutationTask(run, member, reservation, mutationSucceeded);
|
Runnable task = () -> runMutationTask(run, member, reservation, mutationSucceeded);
|
||||||
boolean scheduled;
|
boolean scheduled;
|
||||||
try {
|
try {
|
||||||
scheduled = J.runRegion(
|
scheduled = runMemberRegion(
|
||||||
run.candidate.world(),
|
run,
|
||||||
position.x() >> 4,
|
position.x() >> 4,
|
||||||
position.z() >> 4,
|
position.z() >> 4,
|
||||||
task
|
task
|
||||||
@@ -368,8 +402,10 @@ final class TreeFellingRunner {
|
|||||||
try {
|
try {
|
||||||
if (probe.isCancelled()) {
|
if (probe.isCancelled()) {
|
||||||
if (reservation.charged() || reservation.logCostReserved()) {
|
if (reservation.charged() || reservation.logCostReserved()) {
|
||||||
|
run.abortReason = "probe-cancelled";
|
||||||
refundAndFinish(run, reservation);
|
refundAndFinish(run, reservation);
|
||||||
} else if (member.log()) {
|
} else if (member.log()) {
|
||||||
|
run.abortReason = "probe-cancelled";
|
||||||
finish(run);
|
finish(run);
|
||||||
} else {
|
} else {
|
||||||
continueRun(run);
|
continueRun(run);
|
||||||
@@ -379,6 +415,9 @@ final class TreeFellingRunner {
|
|||||||
|
|
||||||
block = liveMemberBlock(run, member);
|
block = liveMemberBlock(run, member);
|
||||||
if (run.finished.get() || block == null) {
|
if (run.finished.get() || block == null) {
|
||||||
|
if (block == null) {
|
||||||
|
run.abortReason = "member-drift";
|
||||||
|
}
|
||||||
probe.setCancelled(true);
|
probe.setCancelled(true);
|
||||||
refundAndFinish(run, reservation);
|
refundAndFinish(run, reservation);
|
||||||
return;
|
return;
|
||||||
@@ -525,6 +564,7 @@ final class TreeFellingRunner {
|
|||||||
|
|
||||||
private void completeSuccessfulMutation(FellingRun run, DamageReservation reservation) {
|
private void completeSuccessfulMutation(FellingRun run, DamageReservation reservation) {
|
||||||
if (reservation.broke()) {
|
if (reservation.broke()) {
|
||||||
|
run.abortReason = "tool-broke";
|
||||||
finish(run);
|
finish(run);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -564,6 +604,9 @@ final class TreeFellingRunner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
run.presentation.finish();
|
run.presentation.finish();
|
||||||
|
IrisLogging.debug("Tree feller run ended: members=" + run.work.size()
|
||||||
|
+ " processed=" + run.processed.get()
|
||||||
|
+ " reason=" + (run.abortReason == null ? "complete" : run.abortReason));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -573,6 +616,9 @@ final class TreeFellingRunner {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (FellingRun run : List.copyOf(runs)) {
|
for (FellingRun run : List.copyOf(runs)) {
|
||||||
|
if (run.abortReason == null) {
|
||||||
|
run.abortReason = "halted";
|
||||||
|
}
|
||||||
finish(run);
|
finish(run);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ folia-supported: true
|
|||||||
api-version: '${apiVersion}'
|
api-version: '${apiVersion}'
|
||||||
load: STARTUP
|
load: STARTUP
|
||||||
authors: [ cyberpwn, NextdoorPsycho, Vatuu ]
|
authors: [ cyberpwn, NextdoorPsycho, Vatuu ]
|
||||||
website: volmit.com
|
website: VolmitSoftware.com
|
||||||
description: More than a Dimension!
|
description: More than a Dimension!
|
||||||
permissions:
|
permissions:
|
||||||
iris.treefeller:
|
iris.treefeller:
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ softdepend:
|
|||||||
- WorldEdit
|
- WorldEdit
|
||||||
loadbefore: [ Multiverse-Core ]
|
loadbefore: [ Multiverse-Core ]
|
||||||
authors: [ cyberpwn, NextdoorPsycho, Vatuu ]
|
authors: [ cyberpwn, NextdoorPsycho, Vatuu ]
|
||||||
website: volmit.com
|
website: VolmitSoftware.com
|
||||||
description: More than a Dimension!
|
description: More than a Dimension!
|
||||||
commands:
|
commands:
|
||||||
iris:
|
iris:
|
||||||
|
|||||||
@@ -21,10 +21,15 @@ import static org.junit.Assert.assertTrue;
|
|||||||
public class IrisDiagnosticsTest {
|
public class IrisDiagnosticsTest {
|
||||||
@Test
|
@Test
|
||||||
public void serviceInitializationSkipsPackageHelperClasses() throws Exception {
|
public void serviceInitializationSkipsPackageHelperClasses() throws Exception {
|
||||||
Class<?> registrar = Class.forName("art.arcane.iris.core.service.PaperCommandRegistrar");
|
Class<?> registrar = Class.forName("art.arcane.iris.core.commands.PaperCommandRegistrar");
|
||||||
|
|
||||||
assertFalse(Iris.isConcreteImplementation(registrar, IrisService.class));
|
assertFalse(Iris.isConcreteImplementation(registrar, IrisService.class));
|
||||||
assertTrue(Iris.isConcreteImplementation(CommandSVC.class, IrisService.class));
|
assertTrue(Iris.isConcreteImplementation(CommandSVC.class, IrisService.class));
|
||||||
|
// The service package is eagerly Class.forName-scanned by JarScanner at enable; a class
|
||||||
|
// referencing Paper-only types there prints an NCDFE stack trace on plain Spigot.
|
||||||
|
assertFalse(
|
||||||
|
"Paper-only command registrar must stay out of the JarScanner-scanned services package",
|
||||||
|
"art.arcane.iris.core.service".equals(registrar.getPackageName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
+4
-3
@@ -1,5 +1,6 @@
|
|||||||
package art.arcane.iris.core.service;
|
package art.arcane.iris.core.commands;
|
||||||
|
|
||||||
|
import art.arcane.iris.core.service.CommandSVC;
|
||||||
import io.papermc.paper.command.brigadier.BasicCommand;
|
import io.papermc.paper.command.brigadier.BasicCommand;
|
||||||
import io.papermc.paper.command.brigadier.CommandSourceStack;
|
import io.papermc.paper.command.brigadier.CommandSourceStack;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
@@ -74,14 +75,14 @@ public class PaperCommandRegistrarTest {
|
|||||||
private String[] arguments;
|
private String[] arguments;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void executeRoot(CommandSender sender, String label, String[] args) {
|
public void executeRoot(CommandSender sender, String label, String[] args) {
|
||||||
this.sender = sender;
|
this.sender = sender;
|
||||||
this.label = label;
|
this.label = label;
|
||||||
this.arguments = args;
|
this.arguments = args;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
List<String> tabCompleteRoot(CommandSender sender, String alias, String[] args) {
|
public List<String> tabCompleteRoot(CommandSender sender, String alias, String[] args) {
|
||||||
return List.of("first", "second");
|
return List.of("first", "second");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
package art.arcane.iris.core.service;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
public class TreeFellingRunnerPacingTest {
|
||||||
|
@Test
|
||||||
|
public void memberTasksRunInlineOnlyOnNonFoliaMainThread() {
|
||||||
|
assertTrue(TreeFellingRunner.shouldRunInline(false, true));
|
||||||
|
assertFalse(TreeFellingRunner.shouldRunInline(false, false));
|
||||||
|
assertFalse(TreeFellingRunner.shouldRunInline(true, true));
|
||||||
|
assertFalse(TreeFellingRunner.shouldRunInline(true, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,488 +0,0 @@
|
|||||||
[01:29:43] [Test worker/WARN]: Iris cannot resolve the biome id for 'minecraft:plains' before the Minecraft server is available; using biome id 0
|
|
||||||
[01:29:43] [Test worker/WARN]: Iris cannot enumerate the biome registry before the Minecraft server is available; returning no biomes
|
|
||||||
[01:29:43] [Test worker/INFO]: [STDERR]: [Iris/WARN] Can't find block data for minecraft:definitely_not_a_real_block
|
|
||||||
[01:29:43] [Test worker/INFO]: [STDERR]: [Iris/WARN] Can't find block data for minecraft:minecraft:definitely_not_a_real_block
|
|
||||||
[01:29:43] [Test worker/INFO]: [STDERR]: [Iris/WARN] Block 'minecraft:oak_log' rejected state 'not_a_property=x'; using its default state
|
|
||||||
[01:29:43] [Test worker/INFO]: Iris registered custom content provider 'iris_deferred_test'
|
|
||||||
[01:29:43] [Test worker/WARN]: Iris persistent dimension registry entry 1 in /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-partial13614762119230589058/iris-dimensions.json is invalid (entry 1 in /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-partial13614762119230589058/iris-dimensions.json has no dimension); kept verbatim: {"id":"iris:broken","pack":"overworld"}
|
|
||||||
[01:29:43] [Test worker/ERROR]: Iris persistent dimension registry at /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-corrupt-boot13293805067262854379/iris-dimensions.json is corrupt; quarantining it and continuing boot
|
|
||||||
java.lang.IllegalStateException: Iris persistent dimension registry at /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-corrupt-boot13293805067262854379/iris-dimensions.json could not be read; refusing to discard persistent worlds
|
|
||||||
at art.arcane.iris.modded.ModdedDimensionRegistryStore.contents(ModdedDimensionRegistryStore.java:150)
|
|
||||||
at art.arcane.iris.modded.ModdedDimensionRegistryStore.load(ModdedDimensionRegistryStore.java:56)
|
|
||||||
at art.arcane.iris.modded.ModdedDimensionRegistryStore.loadForStartup(ModdedDimensionRegistryStore.java:69)
|
|
||||||
at art.arcane.iris.modded.ModdedDimensionRegistryStoreTest.startupLoadQuarantinesACorruptRegistryInsteadOfFailingBoot(ModdedDimensionRegistryStoreTest.java:99)
|
|
||||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
|
||||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
|
||||||
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
|
|
||||||
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
|
|
||||||
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
|
|
||||||
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
|
|
||||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
|
||||||
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
|
|
||||||
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
|
|
||||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
|
|
||||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
|
|
||||||
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
|
|
||||||
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
|
|
||||||
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
|
|
||||||
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
|
|
||||||
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
|
|
||||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
|
||||||
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
|
|
||||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
|
|
||||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65)
|
|
||||||
at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53)
|
|
||||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
|
||||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
|
||||||
at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77)
|
|
||||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28)
|
|
||||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19)
|
|
||||||
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
|
|
||||||
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88)
|
|
||||||
at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
|
|
||||||
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
|
|
||||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122)
|
|
||||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72)
|
|
||||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
|
|
||||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
|
|
||||||
Caused by: art.arcane.volmlib.util.json.JSONException: A JSONObject text must end with '}' at 34 [character 35 line 1]
|
|
||||||
at art.arcane.volmlib.util.json.JSONTokener.syntaxError(JSONTokener.java:414)
|
|
||||||
at art.arcane.volmlib.util.json.JSONObject.<init>(JSONObject.java:145)
|
|
||||||
at art.arcane.volmlib.util.json.JSONTokener.nextValue(JSONTokener.java:345)
|
|
||||||
at art.arcane.volmlib.util.json.JSONArray.<init>(JSONArray.java:111)
|
|
||||||
at art.arcane.volmlib.util.json.JSONTokener.nextValue(JSONTokener.java:348)
|
|
||||||
at art.arcane.volmlib.util.json.JSONObject.<init>(JSONObject.java:159)
|
|
||||||
at art.arcane.volmlib.util.json.JSONObject.<init>(JSONObject.java:260)
|
|
||||||
at art.arcane.iris.modded.ModdedDimensionRegistryStore.contents(ModdedDimensionRegistryStore.java:117)
|
|
||||||
... 45 more
|
|
||||||
[01:29:43] [Test worker/ERROR]: Iris lost 1 persistent dimension(s) from the corrupt registry: iris:lost
|
|
||||||
[01:29:43] [Test worker/ERROR]: Iris moved the corrupt persistent dimension registry to /var/folders/l1/g6lr3c5x26z_mdmtfzt9gjbr0000gn/T/iris-dimension-registry-corrupt-boot13293805067262854379/iris-dimensions.json.broken-1785648583220
|
|
||||||
[01:29:43] [Test worker/ERROR]: Iris service onDisable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
|
|
||||||
java.lang.RuntimeException: second disable failed
|
|
||||||
at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndNeverRethrows(ModdedServiceManagerTest.java:55)
|
|
||||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
|
||||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
|
||||||
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
|
|
||||||
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
|
|
||||||
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
|
|
||||||
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
|
|
||||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
|
||||||
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
|
|
||||||
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
|
|
||||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
|
|
||||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
|
|
||||||
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
|
|
||||||
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
|
|
||||||
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
|
|
||||||
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
|
|
||||||
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
|
|
||||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
|
||||||
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
|
|
||||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
|
|
||||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65)
|
|
||||||
at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53)
|
|
||||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
|
||||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
|
||||||
at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77)
|
|
||||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28)
|
|
||||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19)
|
|
||||||
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
|
|
||||||
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88)
|
|
||||||
at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
|
|
||||||
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
|
|
||||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122)
|
|
||||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72)
|
|
||||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
|
|
||||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
|
|
||||||
[01:29:43] [Test worker/ERROR]: Iris service onDisable failed for art.arcane.iris.modded.ModdedServiceManagerTest$FirstService
|
|
||||||
java.lang.RuntimeException: first disable failed
|
|
||||||
at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndNeverRethrows(ModdedServiceManagerTest.java:54)
|
|
||||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
|
||||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
|
||||||
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
|
|
||||||
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
|
|
||||||
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
|
|
||||||
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
|
|
||||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
|
||||||
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
|
|
||||||
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
|
|
||||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
|
|
||||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
|
|
||||||
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
|
|
||||||
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
|
|
||||||
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
|
|
||||||
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
|
|
||||||
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
|
|
||||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
|
||||||
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
|
|
||||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
|
|
||||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65)
|
|
||||||
at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53)
|
|
||||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
|
||||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
|
||||||
at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77)
|
|
||||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28)
|
|
||||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19)
|
|
||||||
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
|
|
||||||
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88)
|
|
||||||
at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
|
|
||||||
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
|
|
||||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122)
|
|
||||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72)
|
|
||||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
|
|
||||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
|
|
||||||
[01:29:43] [Test worker/ERROR]: Iris disabled all services with 2 failure(s)
|
|
||||||
java.lang.RuntimeException: second disable failed
|
|
||||||
at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndNeverRethrows(ModdedServiceManagerTest.java:55)
|
|
||||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
|
||||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
|
||||||
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
|
|
||||||
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
|
|
||||||
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
|
|
||||||
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
|
|
||||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
|
||||||
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
|
|
||||||
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
|
|
||||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
|
|
||||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
|
|
||||||
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
|
|
||||||
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
|
|
||||||
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
|
|
||||||
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
|
|
||||||
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
|
|
||||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
|
||||||
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
|
|
||||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
|
|
||||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65)
|
|
||||||
at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53)
|
|
||||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
|
||||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
|
||||||
at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77)
|
|
||||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28)
|
|
||||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19)
|
|
||||||
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
|
|
||||||
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88)
|
|
||||||
at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
|
|
||||||
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
|
|
||||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122)
|
|
||||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72)
|
|
||||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
|
|
||||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
|
|
||||||
Suppressed: java.lang.RuntimeException: first disable failed
|
|
||||||
at art.arcane.iris.modded.ModdedServiceManagerTest.disableAttemptsEveryServiceInReverseOrderAndNeverRethrows(ModdedServiceManagerTest.java:54)
|
|
||||||
... 42 more
|
|
||||||
[01:29:43] [Test worker/ERROR]: Iris service rollback failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
|
|
||||||
java.lang.RuntimeException: cleanup failed
|
|
||||||
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:32)
|
|
||||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
|
||||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
|
||||||
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
|
|
||||||
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
|
|
||||||
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
|
|
||||||
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
|
|
||||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
|
||||||
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
|
|
||||||
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
|
|
||||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
|
|
||||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
|
|
||||||
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
|
|
||||||
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
|
|
||||||
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
|
|
||||||
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
|
|
||||||
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
|
|
||||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
|
||||||
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
|
|
||||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
|
|
||||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65)
|
|
||||||
at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53)
|
|
||||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
|
||||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
|
||||||
at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77)
|
|
||||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28)
|
|
||||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19)
|
|
||||||
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
|
|
||||||
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88)
|
|
||||||
at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
|
|
||||||
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
|
|
||||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122)
|
|
||||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72)
|
|
||||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
|
|
||||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
|
|
||||||
[01:29:43] [Test worker/ERROR]: Iris service onEnable failed for art.arcane.iris.modded.ModdedServiceManagerTest$SecondService
|
|
||||||
java.lang.RuntimeException: enable failed
|
|
||||||
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:31)
|
|
||||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
|
||||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
|
||||||
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
|
|
||||||
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
|
|
||||||
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
|
|
||||||
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
|
|
||||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
|
||||||
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
|
|
||||||
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
|
|
||||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
|
|
||||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
|
|
||||||
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
|
|
||||||
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
|
|
||||||
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
|
|
||||||
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
|
|
||||||
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
|
|
||||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
|
||||||
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
|
|
||||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
|
|
||||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65)
|
|
||||||
at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53)
|
|
||||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
|
||||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
|
||||||
at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77)
|
|
||||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28)
|
|
||||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19)
|
|
||||||
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
|
|
||||||
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88)
|
|
||||||
at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
|
|
||||||
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
|
|
||||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122)
|
|
||||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72)
|
|
||||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
|
|
||||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
|
|
||||||
Suppressed: java.lang.RuntimeException: cleanup failed
|
|
||||||
at art.arcane.iris.modded.ModdedServiceManagerTest.rollsBackEnabledServicesAndPreservesCleanupFailures(ModdedServiceManagerTest.java:32)
|
|
||||||
... 42 more
|
|
||||||
[01:29:43] [Test worker/ERROR]: [worldcheck] server stop request failed
|
|
||||||
java.lang.IllegalStateException: stop request failed
|
|
||||||
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$stopRequestFailureForcesNonzeroResult$1(ModdedWorldCheckTest.java:238)
|
|
||||||
at art.arcane.iris.modded.ModdedWorldCheck.runAndRequestStop(ModdedWorldCheck.java:144)
|
|
||||||
at art.arcane.iris.modded.ModdedWorldCheckTest.stopRequestFailureForcesNonzeroResult(ModdedWorldCheckTest.java:235)
|
|
||||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
|
||||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
|
||||||
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
|
|
||||||
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
|
|
||||||
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
|
|
||||||
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
|
|
||||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
|
||||||
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
|
|
||||||
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
|
|
||||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
|
|
||||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
|
|
||||||
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
|
|
||||||
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
|
|
||||||
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
|
|
||||||
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
|
|
||||||
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
|
|
||||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
|
||||||
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
|
|
||||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
|
|
||||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65)
|
|
||||||
at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53)
|
|
||||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
|
||||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
|
||||||
at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77)
|
|
||||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28)
|
|
||||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19)
|
|
||||||
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
|
|
||||||
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88)
|
|
||||||
at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
|
|
||||||
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
|
|
||||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122)
|
|
||||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72)
|
|
||||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
|
|
||||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
|
|
||||||
[01:29:43] [Test worker/ERROR]: [worldcheck] waiting for server shutdown failed
|
|
||||||
java.lang.IllegalStateException: shutdown wait failed
|
|
||||||
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$shutdownWaitFailureForcesNonzeroExit$0(ModdedWorldCheckTest.java:264)
|
|
||||||
at art.arcane.iris.modded.ModdedWorldCheck.awaitStopAndExit(ModdedWorldCheck.java:159)
|
|
||||||
at art.arcane.iris.modded.ModdedWorldCheckTest.shutdownWaitFailureForcesNonzeroExit(ModdedWorldCheckTest.java:262)
|
|
||||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
|
||||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
|
||||||
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
|
|
||||||
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
|
|
||||||
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
|
|
||||||
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
|
|
||||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
|
||||||
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
|
|
||||||
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
|
|
||||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
|
|
||||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
|
|
||||||
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
|
|
||||||
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
|
|
||||||
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
|
|
||||||
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
|
|
||||||
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
|
|
||||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
|
||||||
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
|
|
||||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
|
|
||||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65)
|
|
||||||
at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53)
|
|
||||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
|
||||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
|
||||||
at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77)
|
|
||||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28)
|
|
||||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19)
|
|
||||||
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
|
|
||||||
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88)
|
|
||||||
at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
|
|
||||||
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
|
|
||||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122)
|
|
||||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72)
|
|
||||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
|
|
||||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
|
|
||||||
[01:29:43] [Test worker/ERROR]: [worldcheck] check failed
|
|
||||||
java.lang.IllegalStateException: check failed
|
|
||||||
at art.arcane.iris.modded.ModdedWorldCheckTest.lambda$thrownCheckStillRequestsStop$0(ModdedWorldCheckTest.java:224)
|
|
||||||
at art.arcane.iris.modded.ModdedWorldCheck.runAndRequestStop(ModdedWorldCheck.java:139)
|
|
||||||
at art.arcane.iris.modded.ModdedWorldCheckTest.thrownCheckStillRequestsStop(ModdedWorldCheckTest.java:222)
|
|
||||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
|
||||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
|
||||||
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
|
|
||||||
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
|
|
||||||
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
|
|
||||||
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
|
|
||||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
|
||||||
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
|
|
||||||
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
|
|
||||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
|
|
||||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
|
|
||||||
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
|
|
||||||
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
|
|
||||||
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
|
|
||||||
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
|
|
||||||
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
|
|
||||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
|
||||||
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
|
|
||||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
|
|
||||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65)
|
|
||||||
at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53)
|
|
||||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
|
||||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
|
||||||
at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77)
|
|
||||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28)
|
|
||||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19)
|
|
||||||
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
|
|
||||||
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88)
|
|
||||||
at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
|
|
||||||
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
|
|
||||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122)
|
|
||||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72)
|
|
||||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
|
|
||||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
|
|
||||||
[01:29:43] [Test worker/INFO]: Iris registered custom content provider 'iris_discovery_success'
|
|
||||||
[01:29:43] [Test worker/WARN]: Iris custom content provider discovery failed at provider 'iris_discovery_failure' (art.arcane.iris.modded.api.ModdedCustomContentRegistryTest$TestProvider)
|
|
||||||
java.lang.RuntimeException: provider init failed
|
|
||||||
at art.arcane.iris.modded.api.ModdedCustomContentRegistryTest.failedDiscoveryPublishesNothingAndPreservesTheCause(ModdedCustomContentRegistryTest.java:39)
|
|
||||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
|
||||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
|
||||||
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
|
|
||||||
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
|
|
||||||
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
|
|
||||||
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
|
|
||||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
|
||||||
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
|
|
||||||
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
|
|
||||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
|
|
||||||
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
|
|
||||||
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
|
|
||||||
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
|
|
||||||
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
|
|
||||||
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
|
|
||||||
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
|
|
||||||
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
|
|
||||||
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
|
|
||||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
|
|
||||||
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.runRequest(JUnitTestExecutor.java:175)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:84)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.JUnitTestExecutor.accept(JUnitTestExecutor.java:47)
|
|
||||||
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestDefinitionProcessor.processTestDefinition(AbstractJUnitTestDefinitionProcessor.java:65)
|
|
||||||
at org.gradle.api.internal.tasks.testing.SuiteTestDefinitionProcessor.processTestDefinition(SuiteTestDefinitionProcessor.java:53)
|
|
||||||
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
|
|
||||||
at java.base/java.lang.reflect.Method.invoke(Method.java:565)
|
|
||||||
at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77)
|
|
||||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28)
|
|
||||||
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19)
|
|
||||||
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
|
|
||||||
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88)
|
|
||||||
at jdk.proxy1/jdk.proxy1.$Proxy4.processTestDefinition(Unknown Source)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:178)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
|
|
||||||
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
|
|
||||||
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
|
|
||||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122)
|
|
||||||
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72)
|
|
||||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
|
|
||||||
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
|
|
||||||
[01:29:43] [Test worker/INFO]: Iris object undo service ready (bounded to 32 paste(s) per player)
|
|
||||||
[01:29:43] [Test worker/INFO]: [STDOUT]: [Iris/INFO] Iris /iris command tree registered
|
|
||||||
[01:29:43] [Test worker/INFO]: [STDOUT]: [Iris/INFO] Iris /iris command tree registered
|
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ public final class ModdedPackInstaller {
|
|||||||
branch,
|
branch,
|
||||||
forceOverwrite,
|
forceOverwrite,
|
||||||
false,
|
false,
|
||||||
|
pack,
|
||||||
feedback) != null;
|
feedback) != null;
|
||||||
if (installed) {
|
if (installed) {
|
||||||
// Pack-install completion is one of the four forced-datapack regeneration triggers; every
|
// Pack-install completion is one of the four forced-datapack regeneration triggers; every
|
||||||
|
|||||||
+6
-2
@@ -114,15 +114,19 @@ public final class ModdedStructureHooks implements PlatformStructureHooks {
|
|||||||
if (structure == null) {
|
if (structure == null) {
|
||||||
throw new IllegalArgumentException("Registered structure does not exist: " + structureKey);
|
throw new IllegalArgumentException("Registered structure does not exist: " + structureKey);
|
||||||
}
|
}
|
||||||
|
boolean hasFilterEntries = false;
|
||||||
for (Holder<Biome> holder : structure.biomes()) {
|
for (Holder<Biome> holder : structure.biomes()) {
|
||||||
|
hasFilterEntries = true;
|
||||||
Optional<ResourceKey<Biome>> key = holder.unwrapKey();
|
Optional<ResourceKey<Biome>> key = holder.unwrapKey();
|
||||||
if (key.isPresent()) {
|
if (key.isPresent()) {
|
||||||
keys.add(key.get().identifier().toString());
|
keys.add(key.get().identifier().toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (keys.isEmpty()) {
|
// An empty biome filter is legal datapack content (opt-in structures whose tags only
|
||||||
|
// reference absent modded biomes); the structure is unreachable, not an error state.
|
||||||
|
if (keys.isEmpty() && hasFilterEntries) {
|
||||||
throw new IllegalStateException("Registered structure '" + structureKey
|
throw new IllegalStateException("Registered structure '" + structureKey
|
||||||
+ "' exposes no registered biome keys");
|
+ "' has biome filter entries but none resolve to registered biome keys");
|
||||||
}
|
}
|
||||||
} catch (RuntimeException error) {
|
} catch (RuntimeException error) {
|
||||||
throw new IllegalStateException("Iris failed to resolve biome keys for registered structure '"
|
throw new IllegalStateException("Iris failed to resolve biome keys for registered structure '"
|
||||||
|
|||||||
@@ -122,6 +122,10 @@ nmsBindings.each { key, value ->
|
|||||||
rootProject.file('adapters/minecraft-common/src/main/java/art/arcane/iris/nativegen/NativeStructurePostProcessor.java').absolutePath)
|
rootProject.file('adapters/minecraft-common/src/main/java/art/arcane/iris/nativegen/NativeStructurePostProcessor.java').absolutePath)
|
||||||
systemProperty('iris.customBiomeSource',
|
systemProperty('iris.customBiomeSource',
|
||||||
rootProject.file("adapters/bukkit/nms/${key}/src/main/java/art/arcane/iris/core/nms/${key}/CustomBiomeSource.java").absolutePath)
|
rootProject.file("adapters/bukkit/nms/${key}/src/main/java/art/arcane/iris/core/nms/${key}/CustomBiomeSource.java").absolutePath)
|
||||||
|
systemProperty('iris.vanillaStructureBiomesSource',
|
||||||
|
rootProject.file("adapters/bukkit/nms/${key}/src/main/java/art/arcane/iris/core/nms/${key}/VanillaStructureBiomes.java").absolutePath)
|
||||||
|
systemProperty('iris.moddedStructureHooksSource',
|
||||||
|
rootProject.file('adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedStructureHooks.java').absolutePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -276,7 +276,13 @@ public class IrisSettings {
|
|||||||
public boolean useCustomColorsIngame = true;
|
public boolean useCustomColorsIngame = true;
|
||||||
public boolean adjustVanillaHeight = false;
|
public boolean adjustVanillaHeight = false;
|
||||||
public boolean autoIngestDatapacks = true;
|
public boolean autoIngestDatapacks = true;
|
||||||
public boolean autoImportDatapackStructures = true;
|
/**
|
||||||
|
* Converting every registered datapack structure into editable Iris resources writes
|
||||||
|
* thousands of objects/pools/pieces into the pack folder. Native generation and
|
||||||
|
* nativeStructures placements never need those copies, so this stays opt-in; run
|
||||||
|
* /iris structure import <dimension> when you actually want editable copies.
|
||||||
|
*/
|
||||||
|
public boolean autoImportDatapackStructures = false;
|
||||||
/** Unresolved pack content keys and bad block-state properties become blocking pack errors. -Diris.strictContent overrides. */
|
/** Unresolved pack content keys and bad block-state properties become blocking pack errors. -Diris.strictContent overrides. */
|
||||||
public boolean strictContentKeys = false;
|
public boolean strictContentKeys = false;
|
||||||
public int spinh = -20;
|
public int spinh = -20;
|
||||||
|
|||||||
@@ -7,19 +7,64 @@ import org.bukkit.World;
|
|||||||
import org.bukkit.generator.WorldInfo;
|
import org.bukkit.generator.WorldInfo;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
public final class IrisWorldStorage {
|
public final class IrisWorldStorage {
|
||||||
private static final String IRIS_NAMESPACE = "iris";
|
private static final String IRIS_NAMESPACE = "iris";
|
||||||
|
private static final String DEFAULT_LEVEL_NAME = "world";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Server#getLevelDirectory is Paper-API-only. Once a call throws NoSuchMethodError (plain
|
||||||
|
* Spigot/CraftBukkit) this flips and every later call goes straight to the fallback.
|
||||||
|
*/
|
||||||
|
private static volatile boolean levelDirectoryUnavailable;
|
||||||
|
private static volatile String cachedFallbackLevelName;
|
||||||
|
|
||||||
private IrisWorldStorage() {
|
private IrisWorldStorage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static File levelRoot() {
|
public static File levelRoot() {
|
||||||
return Bukkit.getServer().getLevelDirectory().toAbsolutePath().normalize().toFile();
|
if (!levelDirectoryUnavailable) {
|
||||||
|
try {
|
||||||
|
return Bukkit.getServer().getLevelDirectory().toAbsolutePath().normalize().toFile();
|
||||||
|
} catch (NoSuchMethodError e) {
|
||||||
|
levelDirectoryUnavailable = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new File(Bukkit.getWorldContainer(), fallbackLevelName()).getAbsoluteFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String fallbackLevelName() {
|
||||||
|
String cached = cachedFallbackLevelName;
|
||||||
|
if (cached == null) {
|
||||||
|
cached = levelNameFromProperties(new File("server.properties"));
|
||||||
|
cachedFallbackLevelName = cached;
|
||||||
|
}
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String levelNameFromProperties(File serverProperties) {
|
||||||
|
Properties properties = new Properties();
|
||||||
|
if (Objects.requireNonNull(serverProperties, "serverProperties").isFile()) {
|
||||||
|
try (InputStream in = new FileInputStream(serverProperties)) {
|
||||||
|
properties.load(in);
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
// Unreadable server.properties: fall through to the default level name.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return levelNameFromProperties(properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
static String levelNameFromProperties(Properties properties) {
|
||||||
|
String levelName = Objects.requireNonNull(properties, "properties").getProperty("level-name", DEFAULT_LEVEL_NAME).trim();
|
||||||
|
return levelName.isEmpty() ? DEFAULT_LEVEL_NAME : levelName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static File levelRoot(File dimensionRoot) {
|
public static File levelRoot(File dimensionRoot) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package art.arcane.iris.core;
|
package art.arcane.iris.core;
|
||||||
|
|
||||||
import art.arcane.iris.core.loader.IrisData;
|
import art.arcane.iris.core.loader.IrisData;
|
||||||
|
import art.arcane.iris.core.pack.PackDownloader;
|
||||||
import art.arcane.iris.core.service.StudioSVC;
|
import art.arcane.iris.core.service.StudioSVC;
|
||||||
import art.arcane.iris.engine.data.cache.AtomicCache;
|
import art.arcane.iris.engine.data.cache.AtomicCache;
|
||||||
import art.arcane.iris.engine.object.IrisDimension;
|
import art.arcane.iris.engine.object.IrisDimension;
|
||||||
@@ -159,6 +160,12 @@ public class IrisWorlds {
|
|||||||
dimension = IrisData.loadAnyDimension(id, null);
|
dimension = IrisData.loadAnyDimension(id, null);
|
||||||
}
|
}
|
||||||
if (dimension == null) {
|
if (dimension == null) {
|
||||||
|
File packsRoot = IrisPlatforms.get().dataFolderNoCreate(StudioSVC.WORKSPACE_NAME);
|
||||||
|
if (PackDownloader.isPackPresent(packsRoot, id)) {
|
||||||
|
IrisLogging.error("Pack '" + id + "' exists at " + new File(packsRoot, id).getPath()
|
||||||
|
+ " but its dimension failed to load; not redownloading. Fix or delete the pack folder.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
IrisLogging.warn("Unable to find dimension type " + id + " Looking for online packs...");
|
IrisLogging.warn("Unable to find dimension type " + id + " Looking for online packs...");
|
||||||
IrisServices.get(StudioSVC.class).downloadSearch(new VolmitSender(Bukkit.getConsoleSender()), id, false);
|
IrisServices.get(StudioSVC.class).downloadSearch(new VolmitSender(Bukkit.getConsoleSender()), id, false);
|
||||||
dimension = IrisData.loadAnyDimension(id, null);
|
dimension = IrisData.loadAnyDimension(id, null);
|
||||||
|
|||||||
@@ -363,6 +363,7 @@ public class ServerConfigurator {
|
|||||||
File[] packs = IrisPlatforms.get().dataFolder("packs").listFiles(File::isDirectory);
|
File[] packs = IrisPlatforms.get().dataFolder("packs").listFiles(File::isDirectory);
|
||||||
Stream<File> locals = packs == null ? Stream.empty() : Arrays.stream(packs);
|
Stream<File> locals = packs == null ? Stream.empty() : Arrays.stream(packs);
|
||||||
return Stream.concat(locals
|
return Stream.concat(locals
|
||||||
|
.filter(base -> !base.getName().contains(".importing-"))
|
||||||
.filter( base -> {
|
.filter( base -> {
|
||||||
var content = new File(base, "dimensions").listFiles();
|
var content = new File(base, "dimensions").listFiles();
|
||||||
return content != null && content.length > 0;
|
return content != null && content.length > 0;
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package art.arcane.iris.core;
|
||||||
|
|
||||||
|
import org.bukkit.NamespacedKey;
|
||||||
|
import org.bukkit.WorldCreator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WorldCreator.ofKey and WorldCreator#key are Paper-API-only. Once a call throws
|
||||||
|
* NoSuchMethodError (plain Spigot/CraftBukkit) this flips and every later call goes
|
||||||
|
* straight to the fallback. The fallback derives names/keys through IrisWorldStorage's
|
||||||
|
* logical mapping so keyFromName(creator.name()) round-trips on Spigot.
|
||||||
|
*/
|
||||||
|
public final class WorldCreatorCompat {
|
||||||
|
private static volatile boolean keyedCreatorsUnavailable;
|
||||||
|
|
||||||
|
private WorldCreatorCompat() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WorldCreator ofKey(NamespacedKey worldKey) {
|
||||||
|
if (!keyedCreatorsUnavailable) {
|
||||||
|
try {
|
||||||
|
return WorldCreator.ofKey(worldKey);
|
||||||
|
} catch (NoSuchMethodError e) {
|
||||||
|
keyedCreatorsUnavailable = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new WorldCreator(IrisWorldStorage.logicalName(worldKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NamespacedKey keyOf(WorldCreator creator) {
|
||||||
|
if (!keyedCreatorsUnavailable) {
|
||||||
|
try {
|
||||||
|
return creator.key();
|
||||||
|
} catch (NoSuchMethodError e) {
|
||||||
|
keyedCreatorsUnavailable = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return IrisWorldStorage.keyFromName(creator.name());
|
||||||
|
}
|
||||||
|
|
||||||
|
static String fallbackName(NamespacedKey worldKey, String levelName) {
|
||||||
|
return IrisWorldStorage.logicalName(worldKey, levelName);
|
||||||
|
}
|
||||||
|
|
||||||
|
static NamespacedKey fallbackKey(String creatorName, String levelName) {
|
||||||
|
return IrisWorldStorage.keyFromName(creatorName, levelName);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -145,7 +145,7 @@ public final class DatapackIngestService {
|
|||||||
|
|
||||||
if (report.changed()) {
|
if (report.changed()) {
|
||||||
message(sender, C.YELLOW + "New datapack structures were installed. A server restart is required for them to register and generate.");
|
message(sender, C.YELLOW + "New datapack structures were installed. A server restart is required for them to register and generate.");
|
||||||
message(sender, C.GRAY + "After the restart their jigsaw pools, pieces & objects are imported automatically (set general.autoImportDatapackStructures=false to disable), or run /iris structure import <dimension> to import everything on demand. Reference an imported key from a 'structures' placement to position it manually.");
|
message(sender, C.GRAY + "After the restart they generate natively - no import needed. To get editable Iris copies (jigsaw pools, pieces & objects written into the pack) run /iris structure import <dimension>, or set general.autoImportDatapackStructures=true to do it on every ingest. Place any registered key directly with a 'structures' placement using nativeStructures.");
|
||||||
message(sender, C.GRAY + "Datapacks replace matching vanilla structure keys by default. Set 'importedStructures.datapackOverrides' to false to keep minecraft-namespaced structure definitions untouched; deny non-minecraft datapack and mod structures explicitly with importedStructures.disabled.");
|
message(sender, C.GRAY + "Datapacks replace matching vanilla structure keys by default. Set 'importedStructures.datapackOverrides' to false to keep minecraft-namespaced structure definitions untouched; deny non-minecraft datapack and mod structures explicitly with importedStructures.disabled.");
|
||||||
if (restart) {
|
if (restart) {
|
||||||
ServerConfigurator.restart();
|
ServerConfigurator.restart();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package art.arcane.iris.core.lifecycle;
|
package art.arcane.iris.core.lifecycle;
|
||||||
|
|
||||||
|
import art.arcane.iris.core.WorldCreatorCompat;
|
||||||
import org.bukkit.NamespacedKey;
|
import org.bukkit.NamespacedKey;
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
import org.bukkit.WorldCreator;
|
import org.bukkit.WorldCreator;
|
||||||
@@ -24,7 +25,7 @@ public record WorldLifecycleRequest(
|
|||||||
public static WorldLifecycleRequest fromCreator(WorldCreator creator, boolean studio, boolean benchmark, WorldLifecycleCaller callerKind) {
|
public static WorldLifecycleRequest fromCreator(WorldCreator creator, boolean studio, boolean benchmark, WorldLifecycleCaller callerKind) {
|
||||||
return new WorldLifecycleRequest(
|
return new WorldLifecycleRequest(
|
||||||
creator.name(),
|
creator.name(),
|
||||||
creator.key(),
|
WorldCreatorCompat.keyOf(creator),
|
||||||
creator.environment(),
|
creator.environment(),
|
||||||
creator.generator(),
|
creator.generator(),
|
||||||
creator.biomeProvider(),
|
creator.biomeProvider(),
|
||||||
@@ -39,7 +40,7 @@ public record WorldLifecycleRequest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
public WorldCreator toWorldCreator() {
|
public WorldCreator toWorldCreator() {
|
||||||
WorldCreator creator = WorldCreator.ofKey(worldKey)
|
WorldCreator creator = WorldCreatorCompat.ofKey(worldKey)
|
||||||
.environment(environment)
|
.environment(environment)
|
||||||
.generateStructures(generateStructures)
|
.generateStructures(generateStructures)
|
||||||
.hardcore(hardcore)
|
.hardcore(hardcore)
|
||||||
|
|||||||
@@ -94,6 +94,10 @@ public final class PackDownloadMessages {
|
|||||||
"iris.runtime.pack_download.acquired",
|
"iris.runtime.pack_download.acquired",
|
||||||
"Successfully acquired {name}."
|
"Successfully acquired {name}."
|
||||||
);
|
);
|
||||||
|
public static final TextKey ALREADY_INSTALLED = TextKey.of(
|
||||||
|
"iris.runtime.pack_download.already_installed",
|
||||||
|
"Pack {key} is already installed, skipping download."
|
||||||
|
);
|
||||||
public static final TextKey VALIDATION_FAILED = TextKey.of(
|
public static final TextKey VALIDATION_FAILED = TextKey.of(
|
||||||
"iris.runtime.pack_download.validation_failed",
|
"iris.runtime.pack_download.validation_failed",
|
||||||
"Pack '{pack}' failed validation; world and Studio creation will be refused. Reasons:"
|
"Pack '{pack}' failed validation; world and Studio creation will be refused. Reasons:"
|
||||||
@@ -136,6 +140,7 @@ public final class PackDownloadMessages {
|
|||||||
DIMENSION_KEY_CONFLICT,
|
DIMENSION_KEY_CONFLICT,
|
||||||
PACK_KEY_CONFLICT,
|
PACK_KEY_CONFLICT,
|
||||||
ACQUIRED,
|
ACQUIRED,
|
||||||
|
ALREADY_INSTALLED,
|
||||||
VALIDATION_FAILED,
|
VALIDATION_FAILED,
|
||||||
VALIDATION_REASON,
|
VALIDATION_REASON,
|
||||||
VALIDATED_WITH_WARNINGS,
|
VALIDATED_WITH_WARNINGS,
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import org.zeroturnaround.zip.commons.FileUtils;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@@ -42,6 +43,7 @@ public final class PackDownloader {
|
|||||||
private static final Pattern GITHUB_REPOSITORY = Pattern.compile("[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+");
|
private static final Pattern GITHUB_REPOSITORY = Pattern.compile("[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+");
|
||||||
private static final Pattern GITHUB_REF = Pattern.compile("[A-Za-z0-9][A-Za-z0-9._/-]*");
|
private static final Pattern GITHUB_REF = Pattern.compile("[A-Za-z0-9][A-Za-z0-9._/-]*");
|
||||||
private static final Pattern COMMIT_SHA = Pattern.compile("[0-9a-fA-F]{40}");
|
private static final Pattern COMMIT_SHA = Pattern.compile("[0-9a-fA-F]{40}");
|
||||||
|
private static final ConcurrentHashMap<String, Object> DOWNLOAD_LOCKS = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private PackDownloader() {
|
private PackDownloader() {
|
||||||
}
|
}
|
||||||
@@ -50,11 +52,51 @@ public final class PackDownloader {
|
|||||||
return DEFAULT_OVERWORLD_PACK.equals(pack);
|
return DEFAULT_OVERWORLD_PACK.equals(pack);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String downloadDefaultOverworld(File packsFolder, boolean forceOverwrite, Consumer<String> feedback) throws IOException {
|
public static String defaultOverworldPack() {
|
||||||
return download(packsFolder, DEFAULT_OVERWORLD_REPOSITORY, defaultOverworldReleaseUrl(), forceOverwrite, true, feedback);
|
return DEFAULT_OVERWORLD_PACK;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String download(File packsFolder, String repo, String ref, boolean forceOverwrite, boolean directUrl, Consumer<String> feedback) throws IOException {
|
/**
|
||||||
|
* Whether a pack folder for {@code key} already exists with at least one dimension file.
|
||||||
|
* Presence is judged on disk, not on loadability: a pack that exists but fails to parse must
|
||||||
|
* surface as an error, never as a redownload. A folder without any dimensions/*.json is a
|
||||||
|
* partial import (an interrupted copy) and counts as absent so it can be replaced.
|
||||||
|
*/
|
||||||
|
public static boolean isPackPresent(File packsFolder, String key) {
|
||||||
|
if (packsFolder == null || key == null || key.isBlank()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
File[] dimensions = new File(new File(packsFolder, key), "dimensions")
|
||||||
|
.listFiles((File dir, String name) -> name.endsWith(".json"));
|
||||||
|
return dimensions != null && dimensions.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String downloadDefaultOverworld(File packsFolder, boolean forceOverwrite, Consumer<String> feedback) throws IOException {
|
||||||
|
return download(packsFolder, DEFAULT_OVERWORLD_REPOSITORY, defaultOverworldReleaseUrl(), forceOverwrite, true, DEFAULT_OVERWORLD_PACK, feedback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Downloads and imports a pack. {@code expectedKey} is the pack key the caller is trying to
|
||||||
|
* obtain (null when unknown, e.g. arbitrary repo/branch downloads); when the key is already
|
||||||
|
* present on disk and {@code forceOverwrite} is false, the network is never touched. The
|
||||||
|
* per-repo lock keeps concurrent startup triggers (async default-pack install racing world
|
||||||
|
* resolution) from downloading the same archive twice.
|
||||||
|
*/
|
||||||
|
public static String download(File packsFolder, String repo, String ref, boolean forceOverwrite, boolean directUrl, String expectedKey, Consumer<String> feedback) throws IOException {
|
||||||
|
// Lock on the destination pack key when known: concurrent triggers for the same pack can
|
||||||
|
// arrive with different refs (release URL vs listing branch) and must still serialize.
|
||||||
|
String lockKey = expectedKey != null && !expectedKey.isBlank() ? "key:" + expectedKey : "ref:" + repo + "|" + ref;
|
||||||
|
Object lock = DOWNLOAD_LOCKS.computeIfAbsent(lockKey, key -> new Object());
|
||||||
|
synchronized (lock) {
|
||||||
|
if (!forceOverwrite && isPackPresent(packsFolder, expectedKey)) {
|
||||||
|
feedback.accept(IrisLanguage.plain(PackDownloadMessages.ALREADY_INSTALLED, MessageArgument.untrusted("key", expectedKey)));
|
||||||
|
return expectedKey;
|
||||||
|
}
|
||||||
|
return downloadLocked(packsFolder, repo, ref, forceOverwrite, directUrl, feedback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String downloadLocked(File packsFolder, String repo, String ref, boolean forceOverwrite, boolean directUrl, Consumer<String> feedback) throws IOException {
|
||||||
String url = directUrl ? ref : resolveGithubArchiveUrl(repo, ref);
|
String url = directUrl ? ref : resolveGithubArchiveUrl(repo, ref);
|
||||||
feedback.accept(IrisLanguage.plain(PackDownloadMessages.DOWNLOADING, MessageArgument.untrusted("url", url)) + " "); //The extra space stops a bug in adventure API from repeating the last letter of the URL
|
feedback.accept(IrisLanguage.plain(PackDownloadMessages.DOWNLOADING, MessageArgument.untrusted("url", url)) + " "); //The extra space stops a bug in adventure API from repeating the last letter of the URL
|
||||||
File zip = WebCache.getNonCachedFile("pack-" + repo, url);
|
File zip = WebCache.getNonCachedFile("pack-" + repo, url);
|
||||||
@@ -122,6 +164,12 @@ public final class PackDownloader {
|
|||||||
String key = d.getLoadKey();
|
String key = d.getLoadKey();
|
||||||
feedback.accept(IrisLanguage.plain(PackDownloadMessages.IMPORTING, MessageArgument.untrusted("name", d.getName()), MessageArgument.untrusted("key", key)));
|
feedback.accept(IrisLanguage.plain(PackDownloadMessages.IMPORTING, MessageArgument.untrusted("name", d.getName()), MessageArgument.untrusted("key", key)));
|
||||||
File packEntry = new File(packsFolder, key);
|
File packEntry = new File(packsFolder, key);
|
||||||
|
File[] staleStaging = packsFolder.listFiles((File parent, String name) -> name.startsWith(key + ".importing-"));
|
||||||
|
if (staleStaging != null) {
|
||||||
|
for (File stale : staleStaging) {
|
||||||
|
IO.delete(stale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (forceOverwrite) {
|
if (forceOverwrite) {
|
||||||
IO.delete(packEntry);
|
IO.delete(packEntry);
|
||||||
@@ -134,11 +182,29 @@ public final class PackDownloader {
|
|||||||
|
|
||||||
File[] existingEntries = packEntry.listFiles();
|
File[] existingEntries = packEntry.listFiles();
|
||||||
if (packEntry.exists() && existingEntries != null && existingEntries.length > 0) {
|
if (packEntry.exists() && existingEntries != null && existingEntries.length > 0) {
|
||||||
feedback.accept(IrisLanguage.plain(PackDownloadMessages.PACK_KEY_CONFLICT, MessageArgument.untrusted("key", key)));
|
if (isPackPresent(packsFolder, key)) {
|
||||||
return null;
|
feedback.accept(IrisLanguage.plain(PackDownloadMessages.PACK_KEY_CONFLICT, MessageArgument.untrusted("key", key)));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Non-empty but no dimension file: a partial import from an interrupted copy.
|
||||||
|
// Replace it instead of refusing forever.
|
||||||
|
IrisLogging.warn("Replacing partial pack folder " + packEntry.getPath() + " (no dimension files found).");
|
||||||
|
IO.delete(packEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
FileUtils.copyDirectory(dir, packEntry);
|
// Stage inside the packs folder and move into place so packs/<key> is never partial:
|
||||||
|
// an interrupted copy previously left a folder without dimensions/, which then blocked
|
||||||
|
// every future import as a key conflict.
|
||||||
|
File staging = new File(packsFolder, key + ".importing-" + UUID.randomUUID());
|
||||||
|
try {
|
||||||
|
FileUtils.copyDirectory(dir, staging);
|
||||||
|
if (!staging.renameTo(packEntry)) {
|
||||||
|
throw new IOException("Unable to move imported pack into place: " + packEntry.getPath());
|
||||||
|
}
|
||||||
|
} catch (IOException | RuntimeException e) {
|
||||||
|
IO.delete(staging);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
IrisData.getLoaded(packEntry)
|
IrisData.getLoaded(packEntry)
|
||||||
.ifPresent(IrisData::hotloaded);
|
.ifPresent(IrisData::hotloaded);
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ public class SchemaBuilder {
|
|||||||
private static final String SYMBOL_LIMIT__N = "*";
|
private static final String SYMBOL_LIMIT__N = "*";
|
||||||
private static final String SYMBOL_TYPE__N = "";
|
private static final String SYMBOL_TYPE__N = "";
|
||||||
private static final String MINECRAFT_NAMESPACE = "minecraft:";
|
private static final String MINECRAFT_NAMESPACE = "minecraft:";
|
||||||
|
/** Namespaced key or family/namespace prefix: "minecraft:village_plains", "minecraft:village", "nova_structures:". */
|
||||||
|
private static final String VANILLA_STRUCTURE_PREFIX_PATTERN = "^[a-z0-9_.-]+:[a-z0-9_./-]*$";
|
||||||
private static volatile JSONArray fontTypes;
|
private static volatile JSONArray fontTypes;
|
||||||
private final KMap<String, JSONObject> definitions;
|
private final KMap<String, JSONObject> definitions;
|
||||||
private final Class<?> root;
|
private final Class<?> root;
|
||||||
@@ -249,6 +251,46 @@ public class SchemaBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A registry enum that ALSO accepts family/namespace prefixes ("minecraft:village",
|
||||||
|
* "nova_structures:") — the runtime prefix-matching contract of importedStructures.disabled and
|
||||||
|
* adjustments[].match. Emitted as anyOf(enum, pattern) so autocomplete still offers registered
|
||||||
|
* keys while prefix entries validate instead of being rejected.
|
||||||
|
*/
|
||||||
|
private void putRegistryEnumOrPrefixRef(JSONObject target, String definitionKey,
|
||||||
|
String enumDefinitionKey, Supplier<JSONArray> values,
|
||||||
|
String pattern) {
|
||||||
|
if (!definitions.containsKey(definitionKey)) {
|
||||||
|
JSONArray anyOf = new JSONArray();
|
||||||
|
JSONObject enumRef = new JSONObject();
|
||||||
|
try {
|
||||||
|
putRegistryEnumRef(enumRef, enumDefinitionKey, values);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
IrisLogging.debug("Schema enum '" + enumDefinitionKey + "' unavailable ("
|
||||||
|
+ e.getMessage() + "); emitting prefix pattern only");
|
||||||
|
}
|
||||||
|
if (enumRef.has("$ref")) {
|
||||||
|
anyOf.put(enumRef);
|
||||||
|
}
|
||||||
|
JSONObject prefix = new JSONObject();
|
||||||
|
prefix.put("type", "string");
|
||||||
|
prefix.put("pattern", pattern);
|
||||||
|
anyOf.put(prefix);
|
||||||
|
JSONObject definition = new JSONObject();
|
||||||
|
definition.put("anyOf", anyOf);
|
||||||
|
definitions.put(definitionKey, definition);
|
||||||
|
}
|
||||||
|
target.put("$ref", "#/definitions/" + definitionKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void putRegistryEnumOrPrefixItems(JSONObject prop, String definitionKey,
|
||||||
|
String enumDefinitionKey, Supplier<JSONArray> values,
|
||||||
|
String pattern) {
|
||||||
|
JSONObject items = new JSONObject();
|
||||||
|
putRegistryEnumOrPrefixRef(items, definitionKey, enumDefinitionKey, values, pattern);
|
||||||
|
prop.put("items", items);
|
||||||
|
}
|
||||||
|
|
||||||
private JSONArray itemTypes() {
|
private JSONArray itemTypes() {
|
||||||
JSONArray a = new JSONArray();
|
JSONArray a = new JSONArray();
|
||||||
for (String key : IrisPlatforms.get().registries().itemKeys()) {
|
for (String key : IrisPlatforms.get().registries().itemKeys()) {
|
||||||
@@ -427,8 +469,14 @@ public class SchemaBuilder {
|
|||||||
|
|
||||||
} else if (k.isAnnotationPresent(RegistryListVanillaStructure.class)) {
|
} else if (k.isAnnotationPresent(RegistryListVanillaStructure.class)) {
|
||||||
fancyType = "Vanilla Structure";
|
fancyType = "Vanilla Structure";
|
||||||
putRegistryEnumRef(prop, "enum-vanilla-structure", this::vanillaStructures);
|
if (k.getAnnotation(RegistryListVanillaStructure.class).prefixes()) {
|
||||||
description.add(SYMBOL_TYPE__N + " Must be a valid vanilla/datapack structure key (use ctrl+space for auto complete!)");
|
putRegistryEnumOrPrefixRef(prop, "enum-vanilla-structure-or-prefix",
|
||||||
|
"enum-vanilla-structure", this::vanillaStructures, VANILLA_STRUCTURE_PREFIX_PATTERN);
|
||||||
|
description.add(SYMBOL_TYPE__N + " Must be a vanilla/datapack structure key or a family/namespace prefix like 'minecraft:village' or 'nova_structures:' (use ctrl+space for auto complete!)");
|
||||||
|
} else {
|
||||||
|
putRegistryEnumRef(prop, "enum-vanilla-structure", this::vanillaStructures);
|
||||||
|
description.add(SYMBOL_TYPE__N + " Must be a valid vanilla/datapack structure key (use ctrl+space for auto complete!)");
|
||||||
|
}
|
||||||
|
|
||||||
} else if (k.isAnnotationPresent(RegistryListVanillaStructureSet.class)) {
|
} else if (k.isAnnotationPresent(RegistryListVanillaStructureSet.class)) {
|
||||||
fancyType = "Vanilla Structure Set";
|
fancyType = "Vanilla Structure Set";
|
||||||
@@ -627,8 +675,14 @@ public class SchemaBuilder {
|
|||||||
description.add(SYMBOL_TYPE__N + " Must be a registered vanilla, datapack, or modded template pool key (use ctrl+space for auto complete!)");
|
description.add(SYMBOL_TYPE__N + " Must be a registered vanilla, datapack, or modded template pool key (use ctrl+space for auto complete!)");
|
||||||
} else if (k.isAnnotationPresent(RegistryListVanillaStructure.class)) {
|
} else if (k.isAnnotationPresent(RegistryListVanillaStructure.class)) {
|
||||||
fancyType = "List<Vanilla Structure>";
|
fancyType = "List<Vanilla Structure>";
|
||||||
putRegistryEnumItems(prop, "enum-vanilla-structure", this::vanillaStructures);
|
if (k.getAnnotation(RegistryListVanillaStructure.class).prefixes()) {
|
||||||
description.add(SYMBOL_TYPE__N + " Must be a valid vanilla/datapack structure key (use ctrl+space for auto complete!)");
|
putRegistryEnumOrPrefixItems(prop, "enum-vanilla-structure-or-prefix",
|
||||||
|
"enum-vanilla-structure", this::vanillaStructures, VANILLA_STRUCTURE_PREFIX_PATTERN);
|
||||||
|
description.add(SYMBOL_TYPE__N + " Must be a vanilla/datapack structure key or a family/namespace prefix like 'minecraft:village' or 'nova_structures:' (use ctrl+space for auto complete!)");
|
||||||
|
} else {
|
||||||
|
putRegistryEnumItems(prop, "enum-vanilla-structure", this::vanillaStructures);
|
||||||
|
description.add(SYMBOL_TYPE__N + " Must be a valid vanilla/datapack structure key (use ctrl+space for auto complete!)");
|
||||||
|
}
|
||||||
} else if (k.isAnnotationPresent(RegistryListVanillaStructureSet.class)) {
|
} else if (k.isAnnotationPresent(RegistryListVanillaStructureSet.class)) {
|
||||||
fancyType = "List<Vanilla Structure Set>";
|
fancyType = "List<Vanilla Structure Set>";
|
||||||
putRegistryEnumItems(prop, "enum-vanilla-structure-set", this::vanillaStructureSets);
|
putRegistryEnumItems(prop, "enum-vanilla-structure-set", this::vanillaStructureSets);
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import art.arcane.iris.core.IrisWorldStorage;
|
|||||||
import art.arcane.iris.core.ServerConfigurator;
|
import art.arcane.iris.core.ServerConfigurator;
|
||||||
import art.arcane.iris.core.lifecycle.WorldLifecycleService;
|
import art.arcane.iris.core.lifecycle.WorldLifecycleService;
|
||||||
import art.arcane.iris.core.loader.IrisData;
|
import art.arcane.iris.core.loader.IrisData;
|
||||||
import art.arcane.iris.core.pack.IrisPack;
|
|
||||||
import art.arcane.iris.core.pack.PackDownloader;
|
import art.arcane.iris.core.pack.PackDownloader;
|
||||||
import art.arcane.iris.core.pack.PackValidationRegistry;
|
import art.arcane.iris.core.pack.PackValidationRegistry;
|
||||||
import art.arcane.iris.core.pack.PackValidationResult;
|
import art.arcane.iris.core.pack.PackValidationResult;
|
||||||
@@ -63,6 +62,7 @@ import java.util.function.Consumer;
|
|||||||
|
|
||||||
import art.arcane.iris.core.localization.BukkitRuntimeMessages;
|
import art.arcane.iris.core.localization.BukkitRuntimeMessages;
|
||||||
import art.arcane.iris.core.localization.IrisLanguage;
|
import art.arcane.iris.core.localization.IrisLanguage;
|
||||||
|
import art.arcane.iris.core.localization.PackDownloadMessages;
|
||||||
import art.arcane.volmlib.util.localization.MessageArgument;
|
import art.arcane.volmlib.util.localization.MessageArgument;
|
||||||
public class StudioSVC implements IrisService {
|
public class StudioSVC implements IrisService {
|
||||||
public static final String LISTING = "https://raw.githubusercontent.com/IrisDimensions/_listing/main/listing-v2.json";
|
public static final String LISTING = "https://raw.githubusercontent.com/IrisDimensions/_listing/main/listing-v2.json";
|
||||||
@@ -76,9 +76,10 @@ public class StudioSVC implements IrisService {
|
|||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
J.a(() -> {
|
J.a(() -> {
|
||||||
String pack = IrisSettings.get().getGenerator().getDefaultWorldType();
|
String pack = IrisSettings.get().getGenerator().getDefaultWorldType();
|
||||||
File f = IrisPack.packsPack(pack);
|
|
||||||
|
|
||||||
if (!f.exists()) {
|
// Presence means a non-empty pack folder: an empty leftover folder must still
|
||||||
|
// trigger the install instead of shadowing it forever.
|
||||||
|
if (!PackDownloader.isPackPresent(getWorkspaceFolder(), pack)) {
|
||||||
if (PackDownloader.isDefaultOverworld(pack)) {
|
if (PackDownloader.isDefaultOverworld(pack)) {
|
||||||
IrisLogging.info("Downloading Default Pack " + pack + " (beta release)");
|
IrisLogging.info("Downloading Default Pack " + pack + " (beta release)");
|
||||||
IrisServices.get(StudioSVC.class).downloadDefaultOverworld(BukkitPlatform.console(), false);
|
IrisServices.get(StudioSVC.class).downloadDefaultOverworld(BukkitPlatform.console(), false);
|
||||||
@@ -203,8 +204,8 @@ public class StudioSVC implements IrisService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// The downloaded pack stays in the packs workspace: deleting it here made the
|
||||||
IO.delete(downloaded);
|
// next startup see a missing pack and download it again.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,6 +232,14 @@ public class StudioSVC implements IrisService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void downloadSearch(VolmitSender sender, String key, boolean forceOverwrite) {
|
public void downloadSearch(VolmitSender sender, String key, boolean forceOverwrite) {
|
||||||
|
// The default overworld always comes from the pinned release
|
||||||
|
// (PackDownloader.DEFAULT_OVERWORLD_RELEASE_URL), never from the listing,
|
||||||
|
// so every code path ships the same pack build.
|
||||||
|
if (PackDownloader.isDefaultOverworld(key)) {
|
||||||
|
downloadDefaultOverworld(sender, forceOverwrite);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String url = getListing(false).get(key);
|
String url = getListing(false).get(key);
|
||||||
|
|
||||||
@@ -244,7 +253,8 @@ public class StudioSVC implements IrisService {
|
|||||||
String[] nodes = url.split("\\Q/\\E");
|
String[] nodes = url.split("\\Q/\\E");
|
||||||
String repo = nodes.length == 1 ? "IrisDimensions/" + nodes[0] : nodes[0] + "/" + nodes[1];
|
String repo = nodes.length == 1 ? "IrisDimensions/" + nodes[0] : nodes[0] + "/" + nodes[1];
|
||||||
String branch = nodes.length > 2 ? nodes[2] : "stable";
|
String branch = nodes.length > 2 ? nodes[2] : "stable";
|
||||||
download(sender, repo, branch, forceOverwrite, false);
|
String expectedKey = key.contains("/") ? null : key;
|
||||||
|
download(sender, repo, branch, forceOverwrite, false, expectedKey);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
IrisLogging.reportError(e);
|
IrisLogging.reportError(e);
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@@ -253,6 +263,13 @@ public class StudioSVC implements IrisService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void downloadDefaultOverworld(VolmitSender sender, boolean forceOverwrite) {
|
public void downloadDefaultOverworld(VolmitSender sender, boolean forceOverwrite) {
|
||||||
|
// Same guard as download(): a present pack must not reach installDataPacks(true),
|
||||||
|
// which can trigger an automatic restart.
|
||||||
|
if (!forceOverwrite && PackDownloader.isPackPresent(getWorkspaceFolder(), PackDownloader.defaultOverworldPack())) {
|
||||||
|
sender.sendMessage(IrisLanguage.text(PackDownloadMessages.ALREADY_INSTALLED, MessageArgument.untrusted("key", PackDownloader.defaultOverworldPack())));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String key = PackDownloader.downloadDefaultOverworld(getWorkspaceFolder(), forceOverwrite, sender::sendMessage);
|
String key = PackDownloader.downloadDefaultOverworld(getWorkspaceFolder(), forceOverwrite, sender::sendMessage);
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
@@ -280,7 +297,18 @@ public class StudioSVC implements IrisService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void download(VolmitSender sender, String repo, String branch, boolean forceOverwrite, boolean directUrl) throws JsonSyntaxException, IOException {
|
public void download(VolmitSender sender, String repo, String branch, boolean forceOverwrite, boolean directUrl) throws JsonSyntaxException, IOException {
|
||||||
String key = PackDownloader.download(getWorkspaceFolder(), repo, branch, forceOverwrite, directUrl, sender::sendMessage);
|
download(sender, repo, branch, forceOverwrite, directUrl, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void download(VolmitSender sender, String repo, String branch, boolean forceOverwrite, boolean directUrl, String expectedKey) throws JsonSyntaxException, IOException {
|
||||||
|
// Skip before PackDownloader so an already-present pack never reaches
|
||||||
|
// installDataPacks(true), which can trigger an automatic restart.
|
||||||
|
if (!forceOverwrite && PackDownloader.isPackPresent(getWorkspaceFolder(), expectedKey)) {
|
||||||
|
sender.sendMessage(IrisLanguage.text(PackDownloadMessages.ALREADY_INSTALLED, MessageArgument.untrusted("key", expectedKey)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String key = PackDownloader.download(getWorkspaceFolder(), repo, branch, forceOverwrite, directUrl, expectedKey, sender::sendMessage);
|
||||||
|
|
||||||
if (key == null) {
|
if (key == null) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -22,12 +22,12 @@ public final class IrisSplashComposer {
|
|||||||
prefix + style.title(" Iris, ") + style.subtitle("Dimension Engine ") + style.tag("[" + releaseTrain + "]"),
|
prefix + style.title(" Iris, ") + style.subtitle("Dimension Engine ") + style.tag("[" + releaseTrain + "]"),
|
||||||
prefix + style.label(" Version: ") + style.value(version),
|
prefix + style.label(" Version: ") + style.value(version),
|
||||||
prefix + style.label(" By: ") + style.value("Volmit Software (Arcane Arts)"),
|
prefix + style.label(" By: ") + style.value("Volmit Software (Arcane Arts)"),
|
||||||
|
prefix + style.label(" Web: ") + style.value("VolmitSoftware.com"),
|
||||||
prefix + style.label(" Server: ") + style.value(serverLine),
|
prefix + style.label(" Server: ") + style.value(serverLine),
|
||||||
prefix + style.label(" Java: ") + style.value(String.valueOf(javaVersion())) + style.label(" | Date: ") + style.value(startupDate()),
|
prefix + style.label(" Java: ") + style.value(String.valueOf(javaVersion())) + style.label(" | Date: ") + style.value(startupDate()),
|
||||||
prefix + style.label(" Commit: ") + style.value(BuildConstants.COMMIT) + style.label("/") + style.value(BuildConstants.ENVIRONMENT),
|
prefix + style.label(" Commit: ") + style.value(BuildConstants.COMMIT) + style.label("/") + style.value(BuildConstants.ENVIRONMENT),
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
"",
|
|
||||||
""
|
""
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
package art.arcane.iris.core.structure;
|
package art.arcane.iris.core.structure;
|
||||||
|
|
||||||
import art.arcane.iris.core.IrisWorldStorage;
|
import art.arcane.iris.core.IrisWorldStorage;
|
||||||
|
import art.arcane.iris.core.WorldCreatorCompat;
|
||||||
import art.arcane.iris.spi.IrisLogging;
|
import art.arcane.iris.spi.IrisLogging;
|
||||||
import art.arcane.iris.core.loader.IrisData;
|
import art.arcane.iris.core.loader.IrisData;
|
||||||
import art.arcane.iris.core.nms.INMS;
|
import art.arcane.iris.core.nms.INMS;
|
||||||
@@ -301,7 +302,7 @@ public final class FeatureImporter {
|
|||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
return existing;
|
return existing;
|
||||||
}
|
}
|
||||||
WorldCreator creator = WorldCreator.ofKey(IrisWorldStorage.keyFromName(SCRATCH_WORLD_NAME))
|
WorldCreator creator = WorldCreatorCompat.ofKey(IrisWorldStorage.keyFromName(SCRATCH_WORLD_NAME))
|
||||||
.environment(World.Environment.NORMAL)
|
.environment(World.Environment.NORMAL)
|
||||||
.type(WorldType.FLAT)
|
.type(WorldType.FLAT)
|
||||||
.generateStructures(false);
|
.generateStructures(false);
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import java.io.ByteArrayOutputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Deque;
|
import java.util.Deque;
|
||||||
@@ -49,9 +50,11 @@ import java.util.LinkedHashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
public final class VillageImporter {
|
public final class VillageImporter {
|
||||||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||||
|
private static final Set<String> PRINTED_FAILURE_SIGNATURES = ConcurrentHashMap.newKeySet();
|
||||||
|
|
||||||
public record Result(boolean success, String message, int pools, int pieces, List<StructureLoss> losses) {
|
public record Result(boolean success, String message, int pools, int pieces, List<StructureLoss> losses) {
|
||||||
public Result {
|
public Result {
|
||||||
@@ -686,25 +689,62 @@ public final class VillageImporter {
|
|||||||
return opt;
|
return opt;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int readIntMember(Object value, String memberName) throws Exception {
|
static int readIntMember(Object value, String memberName) throws Exception {
|
||||||
Class<?> type = value.getClass();
|
Class<?> type = value.getClass();
|
||||||
while (type != null) {
|
while (type != null) {
|
||||||
try {
|
try {
|
||||||
Field field = type.getDeclaredField(memberName);
|
Field field = type.getDeclaredField(memberName);
|
||||||
field.setAccessible(true);
|
field.setAccessible(true);
|
||||||
return field.getInt(value);
|
return coerceInt(field.get(value), memberName, value);
|
||||||
} catch (NoSuchFieldException ignored) {
|
} catch (NoSuchFieldException ignored) {
|
||||||
type = type.getSuperclass();
|
type = type.getSuperclass();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Method method = findMethod(value.getClass(), memberName);
|
Method method = findMethod(value.getClass(), memberName);
|
||||||
if (method != null && Number.class.isAssignableFrom(boxedType(method.getReturnType()))) {
|
if (method != null) {
|
||||||
method.setAccessible(true);
|
method.setAccessible(true);
|
||||||
return ((Number) method.invoke(value)).intValue();
|
return coerceInt(method.invoke(value), memberName, value);
|
||||||
}
|
}
|
||||||
throw new NoSuchFieldException(memberName + " on " + value.getClass().getName());
|
throw new NoSuchFieldException(memberName + " on " + value.getClass().getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Members that are plain numbers on one server build are wrapper objects on another
|
||||||
|
* (JigsawStructure.maxDistanceFromCenter became a MaxDistance{horizontal, vertical} record).
|
||||||
|
* Numbers pass through; a wrapper contributes its horizontal component, else its largest
|
||||||
|
* integral component, so a distance bound is never under-read.
|
||||||
|
*/
|
||||||
|
private static int coerceInt(Object member, String memberName, Object owner) throws Exception {
|
||||||
|
if (member instanceof Number n) {
|
||||||
|
return n.intValue();
|
||||||
|
}
|
||||||
|
if (member == null) {
|
||||||
|
throw new NoSuchFieldException(memberName + " on " + owner.getClass().getName() + " is null");
|
||||||
|
}
|
||||||
|
Method horizontal = findMethod(member.getClass(), "horizontal");
|
||||||
|
if (horizontal != null && Number.class.isAssignableFrom(boxedType(horizontal.getReturnType()))) {
|
||||||
|
horizontal.setAccessible(true);
|
||||||
|
return ((Number) horizontal.invoke(member)).intValue();
|
||||||
|
}
|
||||||
|
Integer widest = null;
|
||||||
|
for (Field component : member.getClass().getDeclaredFields()) {
|
||||||
|
if (Modifier.isStatic(component.getModifiers())
|
||||||
|
|| !Number.class.isAssignableFrom(boxedType(component.getType()))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
component.setAccessible(true);
|
||||||
|
Object componentValue = component.get(member);
|
||||||
|
if (componentValue instanceof Number n && (widest == null || n.intValue() > widest)) {
|
||||||
|
widest = n.intValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (widest != null) {
|
||||||
|
return widest;
|
||||||
|
}
|
||||||
|
throw new NoSuchFieldException(memberName + " on " + owner.getClass().getName()
|
||||||
|
+ " is a " + member.getClass().getName() + " with no integral component");
|
||||||
|
}
|
||||||
|
|
||||||
private static Class<?> boxedType(Class<?> type) {
|
private static Class<?> boxedType(Class<?> type) {
|
||||||
return type == int.class ? Integer.class : type;
|
return type == int.class ? Integer.class : type;
|
||||||
}
|
}
|
||||||
@@ -828,7 +868,28 @@ public final class VillageImporter {
|
|||||||
|
|
||||||
private static void reportFailure(Throwable failure) {
|
private static void reportFailure(Throwable failure) {
|
||||||
IrisLogging.reportError(failure);
|
IrisLogging.reportError(failure);
|
||||||
failure.printStackTrace();
|
if (shouldPrintFullTrace(failure)) {
|
||||||
|
failure.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True the first time a failure signature is seen. A bulk import repeats the same failure once
|
||||||
|
* per registered structure, so printing every trace buries the boot log in hundreds of copies
|
||||||
|
* of one problem; the per-structure "[fail] key: message" line still reports each occurrence.
|
||||||
|
*/
|
||||||
|
static boolean shouldPrintFullTrace(Throwable failure) {
|
||||||
|
if (failure == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
StackTraceElement[] trace = failure.getStackTrace();
|
||||||
|
String signature = failure.getClass().getName() + '|' + failure.getMessage()
|
||||||
|
+ '|' + (trace.length == 0 ? "" : trace[0].toString());
|
||||||
|
return PRINTED_FAILURE_SIGNATURES.add(signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void resetFailureLogState() {
|
||||||
|
PRINTED_FAILURE_SIGNATURES.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void reportWriteFailure(StructureWriteResult result) {
|
private static void reportWriteFailure(StructureWriteResult result) {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import art.arcane.iris.platform.bukkit.BukkitPlatform;
|
|||||||
import art.arcane.iris.core.link.MultiverseCoreLink;
|
import art.arcane.iris.core.link.MultiverseCoreLink;
|
||||||
import art.arcane.iris.core.IrisRuntimeSchedulerMode;
|
import art.arcane.iris.core.IrisRuntimeSchedulerMode;
|
||||||
import art.arcane.iris.core.IrisWorldStorage;
|
import art.arcane.iris.core.IrisWorldStorage;
|
||||||
|
import art.arcane.iris.core.WorldCreatorCompat;
|
||||||
import art.arcane.iris.core.IrisWorlds;
|
import art.arcane.iris.core.IrisWorlds;
|
||||||
import art.arcane.iris.core.IrisSettings;
|
import art.arcane.iris.core.IrisSettings;
|
||||||
import art.arcane.iris.core.ServerConfigurator;
|
import art.arcane.iris.core.ServerConfigurator;
|
||||||
@@ -204,7 +205,7 @@ public class IrisCreator {
|
|||||||
.studio(studio)
|
.studio(studio)
|
||||||
.create();
|
.create();
|
||||||
if (!studio()) {
|
if (!studio()) {
|
||||||
IrisWorlds.get().put(wc.key().toString(), dimension());
|
IrisWorlds.get().put(WorldCreatorCompat.keyOf(wc).toString(), dimension());
|
||||||
}
|
}
|
||||||
ServerConfigurator.installDataPacksIfChanged(!studio());
|
ServerConfigurator.installDataPacksIfChanged(!studio());
|
||||||
IrisLogging.debug("[Studio timing] create.packPrep + datapacks = " + (System.currentTimeMillis() - createStart) + "ms (cumulative in create)");
|
IrisLogging.debug("[Studio timing] create.packPrep + datapacks = " + (System.currentTimeMillis() - createStart) + "ms (cumulative in create)");
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
package art.arcane.iris.core.tools;
|
package art.arcane.iris.core.tools;
|
||||||
|
|
||||||
import art.arcane.iris.core.IrisWorldStorage;
|
import art.arcane.iris.core.IrisWorldStorage;
|
||||||
|
import art.arcane.iris.core.WorldCreatorCompat;
|
||||||
import art.arcane.iris.core.loader.IrisData;
|
import art.arcane.iris.core.loader.IrisData;
|
||||||
import art.arcane.iris.engine.object.IrisDimension;
|
import art.arcane.iris.engine.object.IrisDimension;
|
||||||
import art.arcane.iris.engine.object.IrisWorld;
|
import art.arcane.iris.engine.object.IrisWorld;
|
||||||
@@ -92,7 +93,7 @@ public class IrisWorldCreator {
|
|||||||
new File(w.worldFolder(), "iris/pack"), dimensionName);
|
new File(w.worldFolder(), "iris/pack"), dimensionName);
|
||||||
|
|
||||||
|
|
||||||
return WorldCreator.ofKey(worldKey)
|
return WorldCreatorCompat.ofKey(worldKey)
|
||||||
.environment(environment)
|
.environment(environment)
|
||||||
.generateStructures(true)
|
.generateStructures(true)
|
||||||
.generator(g).seed(seed);
|
.generator(g).seed(seed);
|
||||||
|
|||||||
+7
@@ -39,6 +39,13 @@ public final class NativeStructureGenerationPolicy {
|
|||||||
"Dimension importedStructures must not be null");
|
"Dimension importedStructures must not be null");
|
||||||
IrisNativeStructureDecision decision = control.resolve(structureKey, undergroundStep);
|
IrisNativeStructureDecision decision = control.resolve(structureKey, undergroundStep);
|
||||||
if (!decision.generate()) {
|
if (!decision.generate()) {
|
||||||
|
// A disabled key with an active Iris placement is the "blanket-disable, re-place
|
||||||
|
// explicitly" pattern: the placement planner ignores the disable list, so generation
|
||||||
|
// places it — report REPLACED_BY_IRIS so find/goto/verify locate the placement.
|
||||||
|
if (decision.status() == NativeStructureGenerationStatus.DISABLED_BY_PACK
|
||||||
|
&& IrisStructureLocator.isPlaced(activeEngine, structureKey)) {
|
||||||
|
return decision.withStatus(NativeStructureGenerationStatus.REPLACED_BY_IRIS);
|
||||||
|
}
|
||||||
return decision;
|
return decision;
|
||||||
}
|
}
|
||||||
if (IrisStructureLocator.suppressesVanilla(activeEngine, structureKey)) {
|
if (IrisStructureLocator.suppressesVanilla(activeEngine, structureKey)) {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import java.util.Objects;
|
|||||||
@Data
|
@Data
|
||||||
public class IrisImportedStructureControl {
|
public class IrisImportedStructureControl {
|
||||||
@ArrayType(type = String.class, min = 1)
|
@ArrayType(type = String.class, min = 1)
|
||||||
@RegistryListVanillaStructure
|
@RegistryListVanillaStructure(prefixes = true)
|
||||||
@Desc("Structure keys to deny explicitly, e.g. 'minecraft:stronghold'. A namespace:path prefix also matches, so 'minecraft:village' disables every village variant and 'minecraft:ruined_portal' disables every ruined portal. Every key not matched here remains enabled.")
|
@Desc("Structure keys to deny explicitly, e.g. 'minecraft:stronghold'. A namespace:path prefix also matches, so 'minecraft:village' disables every village variant and 'minecraft:ruined_portal' disables every ruined portal. Every key not matched here remains enabled.")
|
||||||
private KList<String> disabled = new KList<>();
|
private KList<String> disabled = new KList<>();
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -36,7 +36,7 @@ import lombok.experimental.Accessors;
|
|||||||
@Data
|
@Data
|
||||||
public class IrisVanillaStructureAdjustment {
|
public class IrisVanillaStructureAdjustment {
|
||||||
@ArrayType(type = String.class, min = 1)
|
@ArrayType(type = String.class, min = 1)
|
||||||
@RegistryListVanillaStructure
|
@RegistryListVanillaStructure(prefixes = true)
|
||||||
@Desc("Structure keys this adjustment applies to, e.g. 'minecraft:stronghold'. A namespace:path prefix also matches, so 'minecraft:village' adjusts every village variant and 'minecraft:ruined_portal' adjusts every ruined portal. Empty matches nothing.")
|
@Desc("Structure keys this adjustment applies to, e.g. 'minecraft:stronghold'. A namespace:path prefix also matches, so 'minecraft:village' adjusts every village variant and 'minecraft:ruined_portal' adjusts every ruined portal. Empty matches nothing.")
|
||||||
private KList<String> match = new KList<>();
|
private KList<String> match = new KList<>();
|
||||||
|
|
||||||
|
|||||||
+8
-1
@@ -29,5 +29,12 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
|||||||
@Retention(RUNTIME)
|
@Retention(RUNTIME)
|
||||||
@Target({PARAMETER, TYPE, FIELD})
|
@Target({PARAMETER, TYPE, FIELD})
|
||||||
public @interface RegistryListVanillaStructure {
|
public @interface RegistryListVanillaStructure {
|
||||||
|
/**
|
||||||
|
* When true the field accepts family/namespace prefixes in addition to exact registered keys
|
||||||
|
* (the {@code IrisImportedStructureControl.matchesKey} contract: "minecraft:village" matches
|
||||||
|
* every village variant, "nova_structures:" matches a whole namespace). The generated editor
|
||||||
|
* schema then validates entries against the registry enum OR a key/prefix pattern instead of
|
||||||
|
* rejecting anything that is not an exact registered key.
|
||||||
|
*/
|
||||||
|
boolean prefixes() default false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1295,6 +1295,7 @@
|
|||||||
"iris.runtime.pack_download.dimension_key_conflict": "Eine andere Dimension im Pack-Ordner verwendet bereits den Schlüssel {key}. Import fehlgeschlagen!",
|
"iris.runtime.pack_download.dimension_key_conflict": "Eine andere Dimension im Pack-Ordner verwendet bereits den Schlüssel {key}. Import fehlgeschlagen!",
|
||||||
"iris.runtime.pack_download.pack_key_conflict": "Ein anderer Pack verwendet bereits den Schlüssel {key}. Import fehlgeschlagen!",
|
"iris.runtime.pack_download.pack_key_conflict": "Ein anderer Pack verwendet bereits den Schlüssel {key}. Import fehlgeschlagen!",
|
||||||
"iris.runtime.pack_download.acquired": "{name} erfolgreich abgerufen.",
|
"iris.runtime.pack_download.acquired": "{name} erfolgreich abgerufen.",
|
||||||
|
"iris.runtime.pack_download.already_installed": "Pack {key} ist bereits installiert, Download wird übersprungen.",
|
||||||
"iris.runtime.pack_download.validation_failed": "Pack '{pack}' hat die Validierung nicht bestanden; Welt- und Studio-Erstellung werden verweigert. Gründe:",
|
"iris.runtime.pack_download.validation_failed": "Pack '{pack}' hat die Validierung nicht bestanden; Welt- und Studio-Erstellung werden verweigert. Gründe:",
|
||||||
"iris.runtime.pack_download.validation_reason": " - {reason}",
|
"iris.runtime.pack_download.validation_reason": " - {reason}",
|
||||||
"iris.runtime.pack_download.validated_with_warnings": {
|
"iris.runtime.pack_download.validated_with_warnings": {
|
||||||
|
|||||||
@@ -1295,6 +1295,7 @@
|
|||||||
"iris.runtime.pack_download.dimension_key_conflict": "Otra dimensión de la carpeta packs ya usa la clave {key}. ¡La importación falló!",
|
"iris.runtime.pack_download.dimension_key_conflict": "Otra dimensión de la carpeta packs ya usa la clave {key}. ¡La importación falló!",
|
||||||
"iris.runtime.pack_download.pack_key_conflict": "Otro pack usa la clave {key}. ¡La importación falló!",
|
"iris.runtime.pack_download.pack_key_conflict": "Otro pack usa la clave {key}. ¡La importación falló!",
|
||||||
"iris.runtime.pack_download.acquired": "{name} se obtuvo correctamente.",
|
"iris.runtime.pack_download.acquired": "{name} se obtuvo correctamente.",
|
||||||
|
"iris.runtime.pack_download.already_installed": "El pack {key} ya está instalado, se omite la descarga.",
|
||||||
"iris.runtime.pack_download.validation_failed": "El pack '{pack}' no superó la validación; se rechazará la creación de mundos y de Studio. Motivos:",
|
"iris.runtime.pack_download.validation_failed": "El pack '{pack}' no superó la validación; se rechazará la creación de mundos y de Studio. Motivos:",
|
||||||
"iris.runtime.pack_download.validation_reason": " - {reason}",
|
"iris.runtime.pack_download.validation_reason": " - {reason}",
|
||||||
"iris.runtime.pack_download.validated_with_warnings": {
|
"iris.runtime.pack_download.validated_with_warnings": {
|
||||||
|
|||||||
@@ -1295,6 +1295,7 @@
|
|||||||
"iris.runtime.pack_download.dimension_key_conflict": "Toinen ulottuvuus pakkauskansiossa jo käyttää avainta {key}. Tuonti epäonnistui!",
|
"iris.runtime.pack_download.dimension_key_conflict": "Toinen ulottuvuus pakkauskansiossa jo käyttää avainta {key}. Tuonti epäonnistui!",
|
||||||
"iris.runtime.pack_download.pack_key_conflict": "Toinen pakkaus käyttää avainta {key}. Tuonti epäonnistui!",
|
"iris.runtime.pack_download.pack_key_conflict": "Toinen pakkaus käyttää avainta {key}. Tuonti epäonnistui!",
|
||||||
"iris.runtime.pack_download.acquired": "Onnistunut hankinta {name}.",
|
"iris.runtime.pack_download.acquired": "Onnistunut hankinta {name}.",
|
||||||
|
"iris.runtime.pack_download.already_installed": "Pack {key} on jo asennettu, lataus ohitetaan.",
|
||||||
"iris.runtime.pack_download.validation_failed": "Pakkaus{pack}' Epäonnistunut validointi; maailma ja Studio luominen hylätään. Perusteet:",
|
"iris.runtime.pack_download.validation_failed": "Pakkaus{pack}' Epäonnistunut validointi; maailma ja Studio luominen hylätään. Perusteet:",
|
||||||
"iris.runtime.pack_download.validation_reason": " - {reason}",
|
"iris.runtime.pack_download.validation_reason": " - {reason}",
|
||||||
"iris.runtime.pack_download.validated_with_warnings": {
|
"iris.runtime.pack_download.validated_with_warnings": {
|
||||||
|
|||||||
@@ -1295,6 +1295,7 @@
|
|||||||
"iris.runtime.pack_download.dimension_key_conflict": "Une autre dimension du dossier packs utilise déjà la clé {key}. Échec de l'importation !",
|
"iris.runtime.pack_download.dimension_key_conflict": "Une autre dimension du dossier packs utilise déjà la clé {key}. Échec de l'importation !",
|
||||||
"iris.runtime.pack_download.pack_key_conflict": "Un autre pack utilise la clé {key}. Échec de l'importation !",
|
"iris.runtime.pack_download.pack_key_conflict": "Un autre pack utilise la clé {key}. Échec de l'importation !",
|
||||||
"iris.runtime.pack_download.acquired": "{name} obtenu avec succès.",
|
"iris.runtime.pack_download.acquired": "{name} obtenu avec succès.",
|
||||||
|
"iris.runtime.pack_download.already_installed": "Le pack {key} est déjà installé, téléchargement ignoré.",
|
||||||
"iris.runtime.pack_download.validation_failed": "Le pack '{pack}' a échoué à la validation ; la création de mondes et de Studio sera refusée. Raisons :",
|
"iris.runtime.pack_download.validation_failed": "Le pack '{pack}' a échoué à la validation ; la création de mondes et de Studio sera refusée. Raisons :",
|
||||||
"iris.runtime.pack_download.validation_reason": " - {reason}",
|
"iris.runtime.pack_download.validation_reason": " - {reason}",
|
||||||
"iris.runtime.pack_download.validated_with_warnings": {
|
"iris.runtime.pack_download.validated_with_warnings": {
|
||||||
|
|||||||
@@ -1295,6 +1295,7 @@
|
|||||||
"iris.runtime.pack_download.dimension_key_conflict": "מימד נוסף בתיקיה של הלהקה כבר משתמש המפתח {key}. ייבוא נכשל!",
|
"iris.runtime.pack_download.dimension_key_conflict": "מימד נוסף בתיקיה של הלהקה כבר משתמש המפתח {key}. ייבוא נכשל!",
|
||||||
"iris.runtime.pack_download.pack_key_conflict": "חבילה נוספת משתמשת במפתח {key}. ייבוא נכשל!",
|
"iris.runtime.pack_download.pack_key_conflict": "חבילה נוספת משתמשת במפתח {key}. ייבוא נכשל!",
|
||||||
"iris.runtime.pack_download.acquired": "נרכשה בהצלחה {name}.",
|
"iris.runtime.pack_download.acquired": "נרכשה בהצלחה {name}.",
|
||||||
|
"iris.runtime.pack_download.already_installed": "החבילה {key} כבר מותקנת, ההורדה מדולגת.",
|
||||||
"iris.runtime.pack_download.validation_failed": "Pack »{pack}\"התאימות הכושל; יצירת העולם והסטודיו לא תסרב. סיבות:",
|
"iris.runtime.pack_download.validation_failed": "Pack »{pack}\"התאימות הכושל; יצירת העולם והסטודיו לא תסרב. סיבות:",
|
||||||
"iris.runtime.pack_download.validation_reason": " - {reason}",
|
"iris.runtime.pack_download.validation_reason": " - {reason}",
|
||||||
"iris.runtime.pack_download.validated_with_warnings": {
|
"iris.runtime.pack_download.validated_with_warnings": {
|
||||||
|
|||||||
@@ -1295,6 +1295,7 @@
|
|||||||
"iris.runtime.pack_download.dimension_key_conflict": "Un'altra dimensione nella cartella dei Pack usa già la chiave {key}. Importazione non riuscita!",
|
"iris.runtime.pack_download.dimension_key_conflict": "Un'altra dimensione nella cartella dei Pack usa già la chiave {key}. Importazione non riuscita!",
|
||||||
"iris.runtime.pack_download.pack_key_conflict": "Un altro Pack usa già la chiave {key}. Importazione non riuscita!",
|
"iris.runtime.pack_download.pack_key_conflict": "Un altro Pack usa già la chiave {key}. Importazione non riuscita!",
|
||||||
"iris.runtime.pack_download.acquired": "{name} acquisito correttamente.",
|
"iris.runtime.pack_download.acquired": "{name} acquisito correttamente.",
|
||||||
|
"iris.runtime.pack_download.already_installed": "Il pack {key} è già installato, download saltato.",
|
||||||
"iris.runtime.pack_download.validation_failed": "Il Pack '{pack}' non ha superato la convalida; la creazione di mondi e Studio verrà rifiutata. Motivi:",
|
"iris.runtime.pack_download.validation_failed": "Il Pack '{pack}' non ha superato la convalida; la creazione di mondi e Studio verrà rifiutata. Motivi:",
|
||||||
"iris.runtime.pack_download.validation_reason": " - {reason}",
|
"iris.runtime.pack_download.validation_reason": " - {reason}",
|
||||||
"iris.runtime.pack_download.validated_with_warnings": {
|
"iris.runtime.pack_download.validated_with_warnings": {
|
||||||
|
|||||||
@@ -1295,6 +1295,7 @@
|
|||||||
"iris.runtime.pack_download.dimension_key_conflict": "packs フォルダー内の別のディメンションがキー {key} をすでに使用しています。インポートに失敗しました!",
|
"iris.runtime.pack_download.dimension_key_conflict": "packs フォルダー内の別のディメンションがキー {key} をすでに使用しています。インポートに失敗しました!",
|
||||||
"iris.runtime.pack_download.pack_key_conflict": "別のパックがキー {key} を使用しています。インポートに失敗しました!",
|
"iris.runtime.pack_download.pack_key_conflict": "別のパックがキー {key} を使用しています。インポートに失敗しました!",
|
||||||
"iris.runtime.pack_download.acquired": "{name} を取得しました。",
|
"iris.runtime.pack_download.acquired": "{name} を取得しました。",
|
||||||
|
"iris.runtime.pack_download.already_installed": "パック {key} は既にインストールされているため、ダウンロードをスキップします。",
|
||||||
"iris.runtime.pack_download.validation_failed": "パック '{pack}' は検証に失敗しました。ワールドと Studio の作成を拒否します。理由:",
|
"iris.runtime.pack_download.validation_failed": "パック '{pack}' は検証に失敗しました。ワールドと Studio の作成を拒否します。理由:",
|
||||||
"iris.runtime.pack_download.validation_reason": " - {reason}",
|
"iris.runtime.pack_download.validation_reason": " - {reason}",
|
||||||
"iris.runtime.pack_download.validated_with_warnings": {
|
"iris.runtime.pack_download.validated_with_warnings": {
|
||||||
|
|||||||
@@ -1295,6 +1295,7 @@
|
|||||||
"iris.runtime.pack_download.dimension_key_conflict": "packs 폴더의 다른 차원이 이미 키 {key}을(를) 사용합니다. 가져오기에 실패했습니다!",
|
"iris.runtime.pack_download.dimension_key_conflict": "packs 폴더의 다른 차원이 이미 키 {key}을(를) 사용합니다. 가져오기에 실패했습니다!",
|
||||||
"iris.runtime.pack_download.pack_key_conflict": "다른 팩이 키 {key}을(를) 사용합니다. 가져오기에 실패했습니다!",
|
"iris.runtime.pack_download.pack_key_conflict": "다른 팩이 키 {key}을(를) 사용합니다. 가져오기에 실패했습니다!",
|
||||||
"iris.runtime.pack_download.acquired": "성공적으로 취득 {name}.",
|
"iris.runtime.pack_download.acquired": "성공적으로 취득 {name}.",
|
||||||
|
"iris.runtime.pack_download.already_installed": "팩 {key}이(가) 이미 설치되어 있어 다운로드를 건너뜁니다.",
|
||||||
"iris.runtime.pack_download.validation_failed": "팩 '{pack}' 유효성 검사; 세계 및 스튜디오 생성은 거부됩니다. 이유:",
|
"iris.runtime.pack_download.validation_failed": "팩 '{pack}' 유효성 검사; 세계 및 스튜디오 생성은 거부됩니다. 이유:",
|
||||||
"iris.runtime.pack_download.validation_reason": " - {reason}",
|
"iris.runtime.pack_download.validation_reason": " - {reason}",
|
||||||
"iris.runtime.pack_download.validated_with_warnings": {
|
"iris.runtime.pack_download.validated_with_warnings": {
|
||||||
|
|||||||
@@ -1295,6 +1295,7 @@
|
|||||||
"iris.runtime.pack_download.dimension_key_conflict": "Kitas matmuo paketų aplanke jau naudoja raktą {key}. Importuoti nepavyko!",
|
"iris.runtime.pack_download.dimension_key_conflict": "Kitas matmuo paketų aplanke jau naudoja raktą {key}. Importuoti nepavyko!",
|
||||||
"iris.runtime.pack_download.pack_key_conflict": "Kita pakuotė naudoja raktą {key}. Importuoti nepavyko!",
|
"iris.runtime.pack_download.pack_key_conflict": "Kita pakuotė naudoja raktą {key}. Importuoti nepavyko!",
|
||||||
"iris.runtime.pack_download.acquired": "Sėkmingai įgyta {name}.",
|
"iris.runtime.pack_download.acquired": "Sėkmingai įgyta {name}.",
|
||||||
|
"iris.runtime.pack_download.already_installed": "Paketas {key} jau įdiegtas, atsisiuntimas praleidžiamas.",
|
||||||
"iris.runtime.pack_download.validation_failed": "Pakuotė \"{pack}\"nepavyko patvirtinimas; pasaulio ir Studio kūrimas bus atsisakyta. Motyvai:",
|
"iris.runtime.pack_download.validation_failed": "Pakuotė \"{pack}\"nepavyko patvirtinimas; pasaulio ir Studio kūrimas bus atsisakyta. Motyvai:",
|
||||||
"iris.runtime.pack_download.validation_reason": " - {reason}",
|
"iris.runtime.pack_download.validation_reason": " - {reason}",
|
||||||
"iris.runtime.pack_download.validated_with_warnings": {
|
"iris.runtime.pack_download.validated_with_warnings": {
|
||||||
|
|||||||
@@ -1295,6 +1295,7 @@
|
|||||||
"iris.runtime.pack_download.dimension_key_conflict": "Een andere dimensie in de packs-map gebruikt al de sleutel {key}. Importeren mislukt!",
|
"iris.runtime.pack_download.dimension_key_conflict": "Een andere dimensie in de packs-map gebruikt al de sleutel {key}. Importeren mislukt!",
|
||||||
"iris.runtime.pack_download.pack_key_conflict": "Een ander pakje gebruikt de sleutel {key}. Importeren mislukt!",
|
"iris.runtime.pack_download.pack_key_conflict": "Een ander pakje gebruikt de sleutel {key}. Importeren mislukt!",
|
||||||
"iris.runtime.pack_download.acquired": "Succesvol verworven {name}.",
|
"iris.runtime.pack_download.acquired": "Succesvol verworven {name}.",
|
||||||
|
"iris.runtime.pack_download.already_installed": "Pack {key} is al geïnstalleerd, download wordt overgeslagen.",
|
||||||
"iris.runtime.pack_download.validation_failed": "Verpakking{pack}' mislukte validatie; wereld en Studio creatie zal worden geweigerd. Motivering:",
|
"iris.runtime.pack_download.validation_failed": "Verpakking{pack}' mislukte validatie; wereld en Studio creatie zal worden geweigerd. Motivering:",
|
||||||
"iris.runtime.pack_download.validation_reason": " - {reason}",
|
"iris.runtime.pack_download.validation_reason": " - {reason}",
|
||||||
"iris.runtime.pack_download.validated_with_warnings": {
|
"iris.runtime.pack_download.validated_with_warnings": {
|
||||||
|
|||||||
@@ -1295,6 +1295,7 @@
|
|||||||
"iris.runtime.pack_download.dimension_key_conflict": "Inny wymiar w folderze pakietów już używa klucza {key}. Import nie powiódł się!",
|
"iris.runtime.pack_download.dimension_key_conflict": "Inny wymiar w folderze pakietów już używa klucza {key}. Import nie powiódł się!",
|
||||||
"iris.runtime.pack_download.pack_key_conflict": "Inny pakiet używa klucza {key}. Import nie powiódł się!",
|
"iris.runtime.pack_download.pack_key_conflict": "Inny pakiet używa klucza {key}. Import nie powiódł się!",
|
||||||
"iris.runtime.pack_download.acquired": "Udane nabycie {name}.",
|
"iris.runtime.pack_download.acquired": "Udane nabycie {name}.",
|
||||||
|
"iris.runtime.pack_download.already_installed": "Pakiet {key} jest już zainstalowany, pomijanie pobierania.",
|
||||||
"iris.runtime.pack_download.validation_failed": "Paczka \"{pack}\"nieudaną walidację; świat i tworzenie studia zostaną odrzucone. Uzasadnienie:",
|
"iris.runtime.pack_download.validation_failed": "Paczka \"{pack}\"nieudaną walidację; świat i tworzenie studia zostaną odrzucone. Uzasadnienie:",
|
||||||
"iris.runtime.pack_download.validation_reason": " - {reason}",
|
"iris.runtime.pack_download.validation_reason": " - {reason}",
|
||||||
"iris.runtime.pack_download.validated_with_warnings": {
|
"iris.runtime.pack_download.validated_with_warnings": {
|
||||||
|
|||||||
@@ -1295,6 +1295,7 @@
|
|||||||
"iris.runtime.pack_download.dimension_key_conflict": "Outra dimensão na pasta pacotes já está usando a chave {key}. A importação falhou!",
|
"iris.runtime.pack_download.dimension_key_conflict": "Outra dimensão na pasta pacotes já está usando a chave {key}. A importação falhou!",
|
||||||
"iris.runtime.pack_download.pack_key_conflict": "Outro pacote está usando a chave {key}. A importação falhou!",
|
"iris.runtime.pack_download.pack_key_conflict": "Outro pacote está usando a chave {key}. A importação falhou!",
|
||||||
"iris.runtime.pack_download.acquired": "Adquirido com sucesso {name}.",
|
"iris.runtime.pack_download.acquired": "Adquirido com sucesso {name}.",
|
||||||
|
"iris.runtime.pack_download.already_installed": "O pack {key} já está instalado, download ignorado.",
|
||||||
"iris.runtime.pack_download.validation_failed": "Embalar '{pack}' validação falhada; a criação de mundo e estúdio será recusada. Motivos:",
|
"iris.runtime.pack_download.validation_failed": "Embalar '{pack}' validação falhada; a criação de mundo e estúdio será recusada. Motivos:",
|
||||||
"iris.runtime.pack_download.validation_reason": " - {reason}",
|
"iris.runtime.pack_download.validation_reason": " - {reason}",
|
||||||
"iris.runtime.pack_download.validated_with_warnings": {
|
"iris.runtime.pack_download.validated_with_warnings": {
|
||||||
|
|||||||
@@ -1295,6 +1295,7 @@
|
|||||||
"iris.runtime.pack_download.dimension_key_conflict": "Еще одно измерение в папке пакетов уже использует ключ {key}. Импорт провалился!",
|
"iris.runtime.pack_download.dimension_key_conflict": "Еще одно измерение в папке пакетов уже использует ключ {key}. Импорт провалился!",
|
||||||
"iris.runtime.pack_download.pack_key_conflict": "Другой пакет использует ключ. {key}. Импорт провалился!",
|
"iris.runtime.pack_download.pack_key_conflict": "Другой пакет использует ключ. {key}. Импорт провалился!",
|
||||||
"iris.runtime.pack_download.acquired": "Успешно приобретенный {name}.",
|
"iris.runtime.pack_download.acquired": "Успешно приобретенный {name}.",
|
||||||
|
"iris.runtime.pack_download.already_installed": "Пак {key} уже установлен, загрузка пропущена.",
|
||||||
"iris.runtime.pack_download.validation_failed": "Пакуй.{pack}Неудачная проверка; мир и создание студии будут отклонены. Причины:",
|
"iris.runtime.pack_download.validation_failed": "Пакуй.{pack}Неудачная проверка; мир и создание студии будут отклонены. Причины:",
|
||||||
"iris.runtime.pack_download.validation_reason": " - {reason}",
|
"iris.runtime.pack_download.validation_reason": " - {reason}",
|
||||||
"iris.runtime.pack_download.validated_with_warnings": {
|
"iris.runtime.pack_download.validated_with_warnings": {
|
||||||
|
|||||||
@@ -1295,6 +1295,7 @@
|
|||||||
"iris.runtime.pack_download.dimension_key_conflict": "Paketlerdeki başka bir boyut zaten anahtarı kullanıyor {key}. İthalat başarısız oldu!",
|
"iris.runtime.pack_download.dimension_key_conflict": "Paketlerdeki başka bir boyut zaten anahtarı kullanıyor {key}. İthalat başarısız oldu!",
|
||||||
"iris.runtime.pack_download.pack_key_conflict": "Başka bir paket anahtarı kullanıyor {key}. İthalat başarısız oldu!",
|
"iris.runtime.pack_download.pack_key_conflict": "Başka bir paket anahtarı kullanıyor {key}. İthalat başarısız oldu!",
|
||||||
"iris.runtime.pack_download.acquired": "Başarılı bir şekilde satın alındı {name}.",
|
"iris.runtime.pack_download.acquired": "Başarılı bir şekilde satın alındı {name}.",
|
||||||
|
"iris.runtime.pack_download.already_installed": "{key} paketi zaten kurulu, indirme atlanıyor.",
|
||||||
"iris.runtime.pack_download.validation_failed": "Pack \"{pack}“Başarısız doğrulama; dünya ve Stüdyo yaratımı reddedilecektir. Sebepler:",
|
"iris.runtime.pack_download.validation_failed": "Pack \"{pack}“Başarısız doğrulama; dünya ve Stüdyo yaratımı reddedilecektir. Sebepler:",
|
||||||
"iris.runtime.pack_download.validation_reason": " - {reason}",
|
"iris.runtime.pack_download.validation_reason": " - {reason}",
|
||||||
"iris.runtime.pack_download.validated_with_warnings": {
|
"iris.runtime.pack_download.validated_with_warnings": {
|
||||||
|
|||||||
@@ -1295,6 +1295,7 @@
|
|||||||
"iris.runtime.pack_download.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!",
|
"iris.runtime.pack_download.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!",
|
||||||
"iris.runtime.pack_download.pack_key_conflict": "Name {key}. Nhập thất bại!",
|
"iris.runtime.pack_download.pack_key_conflict": "Name {key}. Nhập thất bại!",
|
||||||
"iris.runtime.pack_download.acquired": "Được thành công {name}.",
|
"iris.runtime.pack_download.acquired": "Được thành công {name}.",
|
||||||
|
"iris.runtime.pack_download.already_installed": "Gói {key} đã được cài đặt, bỏ qua tải xuống.",
|
||||||
"iris.runtime.pack_download.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:",
|
"iris.runtime.pack_download.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:",
|
||||||
"iris.runtime.pack_download.validation_reason": " - {reason}",
|
"iris.runtime.pack_download.validation_reason": " - {reason}",
|
||||||
"iris.runtime.pack_download.validated_with_warnings": {
|
"iris.runtime.pack_download.validated_with_warnings": {
|
||||||
|
|||||||
@@ -1295,6 +1295,7 @@
|
|||||||
"iris.runtime.pack_download.dimension_key_conflict": "包文件夹中的另一个维度已经使用密钥 {key}. 导入失败 !",
|
"iris.runtime.pack_download.dimension_key_conflict": "包文件夹中的另一个维度已经使用密钥 {key}. 导入失败 !",
|
||||||
"iris.runtime.pack_download.pack_key_conflict": "另一个包是用钥匙 {key}. 导入失败 !",
|
"iris.runtime.pack_download.pack_key_conflict": "另一个包是用钥匙 {key}. 导入失败 !",
|
||||||
"iris.runtime.pack_download.acquired": "已成功获取 {name}.",
|
"iris.runtime.pack_download.acquired": "已成功获取 {name}.",
|
||||||
|
"iris.runtime.pack_download.already_installed": "包 {key} 已安装,跳过下载。",
|
||||||
"iris.runtime.pack_download.validation_failed": "包{pack}' 验证失败; 世界和工作室的创建将被拒绝. 原因:",
|
"iris.runtime.pack_download.validation_failed": "包{pack}' 验证失败; 世界和工作室的创建将被拒绝. 原因:",
|
||||||
"iris.runtime.pack_download.validation_reason": " - {reason}",
|
"iris.runtime.pack_download.validation_reason": " - {reason}",
|
||||||
"iris.runtime.pack_download.validated_with_warnings": {
|
"iris.runtime.pack_download.validated_with_warnings": {
|
||||||
|
|||||||
@@ -1295,6 +1295,7 @@
|
|||||||
"iris.runtime.pack_download.dimension_key_conflict": "包資料夾中的另一個維度已經使用金鑰 {key}. 匯入失敗 !",
|
"iris.runtime.pack_download.dimension_key_conflict": "包資料夾中的另一個維度已經使用金鑰 {key}. 匯入失敗 !",
|
||||||
"iris.runtime.pack_download.pack_key_conflict": "另一個包是用鑰匙 {key}. 匯入失敗 !",
|
"iris.runtime.pack_download.pack_key_conflict": "另一個包是用鑰匙 {key}. 匯入失敗 !",
|
||||||
"iris.runtime.pack_download.acquired": "已成功獲取 {name}.",
|
"iris.runtime.pack_download.acquired": "已成功獲取 {name}.",
|
||||||
|
"iris.runtime.pack_download.already_installed": "套件 {key} 已安裝,跳過下載。",
|
||||||
"iris.runtime.pack_download.validation_failed": "包{pack}' 驗證失敗; 世界和工作室的建立將被拒絕. 原因:",
|
"iris.runtime.pack_download.validation_failed": "包{pack}' 驗證失敗; 世界和工作室的建立將被拒絕. 原因:",
|
||||||
"iris.runtime.pack_download.validation_reason": " - {reason}",
|
"iris.runtime.pack_download.validation_reason": " - {reason}",
|
||||||
"iris.runtime.pack_download.validated_with_warnings": {
|
"iris.runtime.pack_download.validated_with_warnings": {
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ import org.junit.Test;
|
|||||||
import org.junit.rules.TemporaryFolder;
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertThrows;
|
import static org.junit.Assert.assertThrows;
|
||||||
@@ -49,6 +52,37 @@ public class IrisWorldStorageTest {
|
|||||||
assertEquals("iris_world", IrisWorldStorage.logicalName(new NamespacedKey("iris", "iris_world"), "world"));
|
assertEquals("iris_world", IrisWorldStorage.logicalName(new NamespacedKey("iris", "iris_world"), "world"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readsLevelNameFromServerPropertiesFile() throws Exception {
|
||||||
|
File serverProperties = temporaryFolder.newFile("server.properties");
|
||||||
|
Files.write(serverProperties.toPath(), "level-name=psycho_world\nmotd=A Minecraft Server\n".getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
assertEquals("psycho_world", IrisWorldStorage.levelNameFromProperties(serverProperties));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void defaultsLevelNameWhenServerPropertiesMissing() {
|
||||||
|
File missing = new File(temporaryFolder.getRoot(), "missing/server.properties");
|
||||||
|
|
||||||
|
assertEquals("world", IrisWorldStorage.levelNameFromProperties(missing));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void defaultsLevelNameWhenPropertyAbsentOrBlank() {
|
||||||
|
assertEquals("world", IrisWorldStorage.levelNameFromProperties(new Properties()));
|
||||||
|
|
||||||
|
Properties blank = new Properties();
|
||||||
|
blank.setProperty("level-name", " ");
|
||||||
|
assertEquals("world", IrisWorldStorage.levelNameFromProperties(blank));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void trimsLevelNameFromProperties() {
|
||||||
|
Properties padded = new Properties();
|
||||||
|
padded.setProperty("level-name", " main_level ");
|
||||||
|
assertEquals("main_level", IrisWorldStorage.levelNameFromProperties(padded));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void rejectsKeysThatEscapeNamespaceStorage() throws Exception {
|
public void rejectsKeysThatEscapeNamespaceStorage() throws Exception {
|
||||||
File levelRoot = temporaryFolder.newFolder("world");
|
File levelRoot = temporaryFolder.newFolder("world");
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package art.arcane.iris.core;
|
||||||
|
|
||||||
|
import org.bukkit.NamespacedKey;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
public class WorldCreatorCompatTest {
|
||||||
|
@Test
|
||||||
|
public void keyedPathPreservesWorldKey() {
|
||||||
|
NamespacedKey key = new NamespacedKey("iris", "compat_world");
|
||||||
|
|
||||||
|
assertEquals(key, WorldCreatorCompat.ofKey(key).key());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fallbackNameDerivesLogicalNameFromKey() {
|
||||||
|
assertEquals("compat_world", WorldCreatorCompat.fallbackName(new NamespacedKey("iris", "compat_world"), "world"));
|
||||||
|
assertEquals("world", WorldCreatorCompat.fallbackName(NamespacedKey.minecraft("overworld"), "world"));
|
||||||
|
assertEquals("world_nether", WorldCreatorCompat.fallbackName(NamespacedKey.minecraft("the_nether"), "world"));
|
||||||
|
assertEquals("world_the_end", WorldCreatorCompat.fallbackName(NamespacedKey.minecraft("the_end"), "world"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fallbackKeyRoundTripsCreatorName() {
|
||||||
|
assertEquals(new NamespacedKey("iris", "compat_world"), WorldCreatorCompat.fallbackKey("compat_world", "world"));
|
||||||
|
assertEquals(NamespacedKey.minecraft("overworld"), WorldCreatorCompat.fallbackKey("world", "world"));
|
||||||
|
assertEquals(NamespacedKey.minecraft("the_nether"), WorldCreatorCompat.fallbackKey("world_nether", "world"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fallbackMappingIsStableAcrossRoundTrips() {
|
||||||
|
NamespacedKey key = new NamespacedKey("iris", "iris_world");
|
||||||
|
|
||||||
|
String name = WorldCreatorCompat.fallbackName(key, "world");
|
||||||
|
|
||||||
|
assertEquals(key, WorldCreatorCompat.fallbackKey(name, "world"));
|
||||||
|
assertEquals(name, WorldCreatorCompat.fallbackName(key, "world"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,7 +18,15 @@
|
|||||||
|
|
||||||
package art.arcane.iris.core.pack;
|
package art.arcane.iris.core.pack;
|
||||||
|
|
||||||
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
@@ -26,6 +34,8 @@ import static org.junit.Assert.assertThrows;
|
|||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
public class PackDownloaderTest {
|
public class PackDownloaderTest {
|
||||||
|
@Rule
|
||||||
|
public TemporaryFolder temp = new TemporaryFolder();
|
||||||
@Test
|
@Test
|
||||||
public void resolvesDefaultOverworldBetaRelease() {
|
public void resolvesDefaultOverworldBetaRelease() {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
@@ -74,6 +84,55 @@ public class PackDownloaderTest {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isPackPresentRequiresNonEmptyFolder() throws IOException {
|
||||||
|
File packsFolder = temp.newFolder("packs");
|
||||||
|
|
||||||
|
assertFalse(PackDownloader.isPackPresent(packsFolder, "overworld"));
|
||||||
|
assertFalse(PackDownloader.isPackPresent(packsFolder, null));
|
||||||
|
assertFalse(PackDownloader.isPackPresent(packsFolder, ""));
|
||||||
|
assertFalse(PackDownloader.isPackPresent(null, "overworld"));
|
||||||
|
|
||||||
|
File pack = new File(packsFolder, "overworld");
|
||||||
|
assertTrue(pack.mkdirs());
|
||||||
|
assertFalse(PackDownloader.isPackPresent(packsFolder, "overworld"));
|
||||||
|
|
||||||
|
// A partial import (content but no dimension file) counts as absent so it can be replaced.
|
||||||
|
File biomes = new File(pack, "biomes");
|
||||||
|
assertTrue(biomes.mkdirs());
|
||||||
|
Files.writeString(new File(biomes, "plains.json").toPath(), "{}");
|
||||||
|
assertFalse(PackDownloader.isPackPresent(packsFolder, "overworld"));
|
||||||
|
|
||||||
|
File dimensions = new File(pack, "dimensions");
|
||||||
|
assertTrue(dimensions.mkdirs());
|
||||||
|
Files.writeString(new File(dimensions, "overworld.json").toPath(), "{}");
|
||||||
|
assertTrue(PackDownloader.isPackPresent(packsFolder, "overworld"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void downloadSkipsWhenExpectedPackAlreadyPresent() throws IOException {
|
||||||
|
File packsFolder = temp.newFolder("packs");
|
||||||
|
File dimensions = new File(packsFolder, "overworld/dimensions");
|
||||||
|
assertTrue(dimensions.mkdirs());
|
||||||
|
Files.writeString(new File(dimensions, "overworld.json").toPath(), "{}");
|
||||||
|
|
||||||
|
List<String> feedback = new ArrayList<>();
|
||||||
|
// The URL is unreachable on purpose: reaching the network would fail the download and
|
||||||
|
// return null, so a non-null key proves the presence check ran before any fetch.
|
||||||
|
String key = PackDownloader.download(
|
||||||
|
packsFolder,
|
||||||
|
"IrisDimensions/overworld",
|
||||||
|
"http://127.0.0.1:9/unreachable.zip",
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
"overworld",
|
||||||
|
feedback::add
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEquals("overworld", key);
|
||||||
|
assertFalse(feedback.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void rejectsUnsafeRepositoryAndReference() {
|
public void rejectsUnsafeRepositoryAndReference() {
|
||||||
assertThrows(IllegalArgumentException.class, () -> PackDownloader.resolveGithubArchiveUrl("IrisDimensions/overworld?raw=1", "master"));
|
assertThrows(IllegalArgumentException.class, () -> PackDownloader.resolveGithubArchiveUrl("IrisDimensions/overworld?raw=1", "master"));
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package art.arcane.iris.core.project;
|
||||||
|
|
||||||
|
import art.arcane.iris.engine.object.IrisImportedStructureControl;
|
||||||
|
import art.arcane.iris.engine.object.IrisVanillaStructureAdjustment;
|
||||||
|
import art.arcane.iris.engine.object.annotations.ArrayType;
|
||||||
|
import art.arcane.iris.engine.object.annotations.Desc;
|
||||||
|
import art.arcane.iris.engine.object.annotations.RegistryListVanillaStructure;
|
||||||
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
|
import art.arcane.volmlib.util.json.JSONArray;
|
||||||
|
import art.arcane.volmlib.util.json.JSONObject;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* importedStructures.disabled and adjustments[].match accept family/namespace PREFIXES
|
||||||
|
* ("minecraft:village", "nova_structures:") per IrisImportedStructureControl.matchesKey, so the
|
||||||
|
* generated editor schema must not reject them with a strict registered-key enum. Prefix-capable
|
||||||
|
* fields emit an anyOf of the registry enum plus a key/prefix pattern; exact-key fields (e.g.
|
||||||
|
* nativeStructures[].structure) keep the strict enum.
|
||||||
|
*/
|
||||||
|
public class VanillaStructurePrefixSchemaTest {
|
||||||
|
@Test
|
||||||
|
public void prefixCapableFieldsDeclareThePrefixAnnotation() throws NoSuchFieldException {
|
||||||
|
assertTrue(IrisImportedStructureControl.class.getDeclaredField("disabled")
|
||||||
|
.getAnnotation(RegistryListVanillaStructure.class).prefixes());
|
||||||
|
assertTrue(IrisVanillaStructureAdjustment.class.getDeclaredField("match")
|
||||||
|
.getAnnotation(RegistryListVanillaStructure.class).prefixes());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void prefixListSchemaAcceptsFamilyAndNamespacePrefixes() {
|
||||||
|
JSONObject schema = new SchemaBuilder(PrefixModel.class, null).construct();
|
||||||
|
JSONObject items = schema.getJSONObject("properties").getJSONObject("disabled")
|
||||||
|
.getJSONObject("items");
|
||||||
|
String definitionKey = items.getString("$ref").substring("#/definitions/".length());
|
||||||
|
JSONArray anyOf = schema.getJSONObject("definitions").getJSONObject(definitionKey)
|
||||||
|
.getJSONArray("anyOf");
|
||||||
|
|
||||||
|
String pattern = null;
|
||||||
|
for (int i = 0; i < anyOf.length(); i++) {
|
||||||
|
JSONObject branch = anyOf.getJSONObject(i);
|
||||||
|
if (branch.has("pattern")) {
|
||||||
|
pattern = branch.getString("pattern");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertTrue("anyOf must contain a pattern branch for prefixes", pattern != null);
|
||||||
|
|
||||||
|
Pattern compiled = Pattern.compile(pattern);
|
||||||
|
assertTrue(compiled.matcher("minecraft:village").matches());
|
||||||
|
assertTrue(compiled.matcher("minecraft:pillager_outpost").matches());
|
||||||
|
assertTrue(compiled.matcher("nova_structures:").matches());
|
||||||
|
assertTrue(compiled.matcher("towns_and_towers:exclusives/village_piglin").matches());
|
||||||
|
assertFalse(compiled.matcher("village").matches());
|
||||||
|
assertFalse(compiled.matcher("Nova Structures:tavern").matches());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Desc("Schema model for prefix-capable vanilla structure lists.")
|
||||||
|
public static class PrefixModel {
|
||||||
|
@RegistryListVanillaStructure(prefixes = true)
|
||||||
|
@ArrayType(type = String.class, min = 1)
|
||||||
|
@Desc("Prefix-capable deny list.")
|
||||||
|
private KList<String> disabled = new KList<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,4 +14,14 @@ public class IrisSplashComposerTest {
|
|||||||
assertEquals(" Version: 4.0.0-26.2", info[2]);
|
assertEquals(" Version: 4.0.0-26.2", info[2]);
|
||||||
assertFalse(String.join("\n", info).contains("RC.1.1.6"));
|
assertFalse(String.join("\n", info).contains("RC.1.1.6"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void composeInfoShowsWebsiteAndKeepsSplashHeight() {
|
||||||
|
String[] info = IrisSplashComposer.composeInfo("4.0.0-26.2", "Paper 26.2", IrisSplashComposer.InfoStyle.PLAIN);
|
||||||
|
|
||||||
|
assertEquals(11, info.length);
|
||||||
|
assertEquals(" By: Volmit Software (Arcane Arts)", info[3]);
|
||||||
|
assertEquals(" Web: VolmitSoftware.com", info[4]);
|
||||||
|
assertEquals(" Server: Paper 26.2", info[5]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package art.arcane.iris.core.structure;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A bulk import walks every registered datapack structure, so one broken server-side assumption
|
||||||
|
* fails hundreds of times in a row from the same throw site. Printing a full stack trace per
|
||||||
|
* structure buried a Leaf 26.2 boot under ~4,700 identical lines. The first occurrence of a failure
|
||||||
|
* signature prints in full; repeats are counted by the per-structure "[fail] key: message" line
|
||||||
|
* instead of re-printing.
|
||||||
|
*/
|
||||||
|
public class VillageImporterFailureLogTest {
|
||||||
|
/** One throw site, mirroring a repeated in-loop failure (same class, message, and top frame). */
|
||||||
|
private static Throwable raise(boolean illegalArgument, String message) {
|
||||||
|
return illegalArgument ? new IllegalArgumentException(message) : new IllegalStateException(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void repeatedIdenticalFailuresPrintOnce() {
|
||||||
|
VillageImporter.resetFailureLogState();
|
||||||
|
|
||||||
|
assertTrue(VillageImporter.shouldPrintFullTrace(
|
||||||
|
raise(true, "illegal data type conversion to int")));
|
||||||
|
for (int i = 0; i < 200; i++) {
|
||||||
|
assertFalse(VillageImporter.shouldPrintFullTrace(
|
||||||
|
raise(true, "illegal data type conversion to int")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void distinctFailuresEachPrintOnce() {
|
||||||
|
VillageImporter.resetFailureLogState();
|
||||||
|
|
||||||
|
assertTrue(VillageImporter.shouldPrintFullTrace(raise(true, "a")));
|
||||||
|
assertTrue(VillageImporter.shouldPrintFullTrace(raise(false, "a")));
|
||||||
|
assertTrue(VillageImporter.shouldPrintFullTrace(raise(true, "b")));
|
||||||
|
assertFalse(VillageImporter.shouldPrintFullTrace(raise(true, "b")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void nullFailureIsNotPrinted() {
|
||||||
|
VillageImporter.resetFailureLogState();
|
||||||
|
assertFalse(VillageImporter.shouldPrintFullTrace(null));
|
||||||
|
}
|
||||||
|
}
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
package art.arcane.iris.core.structure;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JigsawStructure.maxDistanceFromCenter is a plain int on some server builds and a
|
||||||
|
* JigsawStructure$MaxDistance record ({int horizontal, int vertical}) on others (e.g. Leaf
|
||||||
|
* 26.2-33). The reflective member reader must handle both — the wrapper shape previously threw
|
||||||
|
* IllegalArgumentException from Field.getInt, failing the jigsaw import of every datapack
|
||||||
|
* structure ("Failed to read jigsaw structure graph").
|
||||||
|
*/
|
||||||
|
public class VillageImporterMaxDistanceReadTest {
|
||||||
|
private static final class IntShape {
|
||||||
|
private final int maxDistanceFromCenter = 80;
|
||||||
|
}
|
||||||
|
|
||||||
|
private record MaxDistance(int horizontal, int vertical) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class WrapperShape {
|
||||||
|
private final MaxDistance maxDistanceFromCenter = new MaxDistance(96, 48);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class BoxedShape {
|
||||||
|
private final Integer maxDistanceFromCenter = 64;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readsPlainIntField() throws Exception {
|
||||||
|
assertEquals(80, VillageImporter.readIntMember(new IntShape(), "maxDistanceFromCenter"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readsLargestIntComponentFromWrapperRecord() throws Exception {
|
||||||
|
assertEquals(96, VillageImporter.readIntMember(new WrapperShape(), "maxDistanceFromCenter"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readsBoxedNumberField() throws Exception {
|
||||||
|
assertEquals(64, VillageImporter.readIntMember(new BoxedShape(), "maxDistanceFromCenter"));
|
||||||
|
}
|
||||||
|
}
|
||||||
+60
@@ -1,9 +1,20 @@
|
|||||||
package art.arcane.iris.engine.framework;
|
package art.arcane.iris.engine.framework;
|
||||||
|
|
||||||
|
import art.arcane.iris.core.loader.IrisData;
|
||||||
|
import art.arcane.iris.engine.object.IrisDimension;
|
||||||
|
import art.arcane.iris.engine.object.IrisImportedStructureControl;
|
||||||
|
import art.arcane.iris.engine.object.IrisNativeStructure;
|
||||||
|
import art.arcane.iris.engine.object.IrisNativeStructureDecision;
|
||||||
|
import art.arcane.iris.engine.object.IrisRegion;
|
||||||
|
import art.arcane.iris.engine.object.IrisStructurePlacement;
|
||||||
import art.arcane.iris.engine.object.NativeStructureGenerationStatus;
|
import art.arcane.iris.engine.object.NativeStructureGenerationStatus;
|
||||||
|
import art.arcane.volmlib.util.collection.KList;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
public class NativeStructureGenerationPolicyTest {
|
public class NativeStructureGenerationPolicyTest {
|
||||||
@Test
|
@Test
|
||||||
@@ -17,4 +28,53 @@ public class NativeStructureGenerationPolicyTest {
|
|||||||
NativeStructureGenerationPolicy.generationStatusMessage(
|
NativeStructureGenerationPolicy.generationStatusMessage(
|
||||||
"minecraft:ancient_city", NativeStructureGenerationStatus.REPLACED_BY_IRIS));
|
"minecraft:ancient_city", NativeStructureGenerationStatus.REPLACED_BY_IRIS));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The "blanket-disable a namespace, re-place chosen structures via nativeStructures placements"
|
||||||
|
* pattern: generation honors the placement (the planner never consults the disable list), so the
|
||||||
|
* policy must report those keys as REPLACED_BY_IRIS — making /iris find|goto and
|
||||||
|
* /iris structure verify locate the placement instead of claiming the key is disabled.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void disabledKeyWithActivePlacementResolvesAsReplacedByIris() {
|
||||||
|
Engine engine = engineWithDisabledNamespaceAndRegionPlacement("nova_structures:tavern_oak");
|
||||||
|
|
||||||
|
assertEquals(NativeStructureGenerationStatus.REPLACED_BY_IRIS,
|
||||||
|
NativeStructureGenerationPolicy.resolve(engine, "nova_structures:tavern_oak", false).status());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void disabledKeyWithoutPlacementStaysDisabled() {
|
||||||
|
Engine engine = engineWithDisabledNamespaceAndRegionPlacement("nova_structures:tavern_oak");
|
||||||
|
|
||||||
|
IrisNativeStructureDecision decision =
|
||||||
|
NativeStructureGenerationPolicy.resolve(engine, "nova_structures:witch_villa", false);
|
||||||
|
assertEquals(NativeStructureGenerationStatus.DISABLED_BY_PACK, decision.status());
|
||||||
|
assertFalse(decision.generate());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Engine engineWithDisabledNamespaceAndRegionPlacement(String placedKey) {
|
||||||
|
IrisData data = mock(IrisData.class);
|
||||||
|
Engine engine = mock(Engine.class);
|
||||||
|
IrisDimension dimension = mock(IrisDimension.class);
|
||||||
|
IrisImportedStructureControl control = new IrisImportedStructureControl();
|
||||||
|
control.getDisabled().add("nova_structures:");
|
||||||
|
|
||||||
|
IrisStructurePlacement placement = new IrisStructurePlacement();
|
||||||
|
placement.getNativeStructures().add(new IrisNativeStructure().setStructure(placedKey));
|
||||||
|
IrisRegion region = mock(IrisRegion.class);
|
||||||
|
KList<IrisStructurePlacement> regionPlacements = new KList<>();
|
||||||
|
regionPlacements.add(placement);
|
||||||
|
when(region.getStructures()).thenReturn(regionPlacements);
|
||||||
|
KList<IrisRegion> regions = new KList<>();
|
||||||
|
regions.add(region);
|
||||||
|
|
||||||
|
when(engine.getData()).thenReturn(data);
|
||||||
|
when(engine.getDimension()).thenReturn(dimension);
|
||||||
|
when(dimension.getImportedStructures()).thenReturn(control);
|
||||||
|
when(dimension.getStructures()).thenReturn(new KList<>());
|
||||||
|
when(dimension.getAllRegions(engine)).thenReturn(regions);
|
||||||
|
when(dimension.getReachableBiomes(engine)).thenReturn(new KList<>());
|
||||||
|
return engine;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user